使用带有原子<bool>的std :: condition_variable

时间:2016-03-21 08:40:24

标签: c++ multithreading c++11

SO处理原子有几个问题,而其他问题涉及std :: condition_variable。但我的问题是,如果我在下面的使用是正确的吗?

三个线程,一个ctrl线程,在取消其他两个线程之前进行准备工作。 ctrl线程还可以在工作线程(发送器/接收器)处于紧密的发送/接收循环时暂停它们。 使用原子的想法是在没有设置暂停的布尔值的情况下使紧密循环更快。

class SomeClass
{

public:
    //...                                                                                                                                                                                                                                                                                                                                                                                   
    // Disregard that data is public...                                                                                                                                                                                                                                                                                                                                                     

    std::condition_variable cv; // UDP threads will wait on this cv until allowed                                                                                                                                                                                                                                                                                                           
                                // to run by ctrl thread.                                                                                                                                                                                                                                                                                                                                   
    std::mutex cv_m;
    std::atomic<bool> pause_test_threads;
};

void do_pause_test_threads(SomeClass *someclass)
{
    if (!someclass->pause_test_threads)
    {
        // Even though we use an atomic, mutex must be held during                                                                                                                                                                                                                                                                                                                          
        // modification. See documentation of condition variable                                                                                                                                                                                                                                                                                                                            
        // notify_all/wait. Mutex does not need to be held for the actual                                                                                                                                                                                                                                                                                                                   
        // notify call.                                                                                                                                                                                                                                                                                                                                                                     
        std::lock_guard<std::mutex> lk(someclass->cv_m);
        someclass->pause_test_threads = true;
    }
}

void unpause_test_threads(SomeClass *someclass)
{
    if (someclass->pause_test_threads)
    {
        {
            // Even though we use an atomic, mutex must be held during                                                                                                                                                                                                                                                                                                                      
            // modification. See documentation of condition variable                                                                                                                                                                                                                                                                                                                        
            // notify_all/wait. Mutex does not need to be held for the actual                                                                                                                                                                                                                                                                                                               
            // notify call.                                                                                                                                                                                                                                                                                                                                                                 
            std::lock_guard<std::mutex> lk(someclass->cv_m);
            someclass->pause_test_threads = false;
        }
        someclass->cv.notify_all(); // Allow send/receive threads to run.                                                                                                                                                                                                                                                                                                                   
    }
}

void wait_to_start(SomeClass *someclass)
{
    std::unique_lock<std::mutex> lk(someclass->cv_m); // RAII, no need for unlock.                                                                                                                                                                                                                                                                                                          
    auto not_paused = [someclass](){return someclass->pause_test_threads == false;};
    someclass->cv.wait(lk, not_paused);
}

void ctrl_thread(SomeClass *someclass)
{
    // Do startup work                                                                                                                                                                                                                                                                                                                                                                      
    // ...                                                                                                                                                                                                                                                                                                                                                                                  
    unpause_test_threads(someclass);

    for (;;)
    {
        // ... check for end-program etc, if so, break;                                                                                                                                                                                                                                                                                                                                     
        if (lost ctrl connection to other endpoint)
        {
            pause_test_threads();
        }
        else
        {
            unpause_test_threads();
        }
        sleep(SLEEP_INTERVAL);

    }

    unpause_test_threads(someclass);
}

void sender_thread(SomeClass *someclass)
{
    wait_to_start(someclass);
    ...
    for (;;)
    {
        // ... check for end-program etc, if so, break;                                                                                                                                                                                                                                                                                                                                     
        if (someclass->pause_test_threads) wait_to_start(someclass);
        ...
    }
}

void receiver_thread(SomeClass *someclass)
{
    wait_to_start(someclass);
    ...
    for (;;)
    {
        // ... check for end-program etc, if so, break;                                                                                                                                                                                                                                                                                                                                     
        if (someclass->pause_test_threads) wait_to_start(someclass);
        ...
    }

3 个答案:

答案 0 :(得分:9)

我查看了你的代码操纵条件变量和原子,看起来它是正确的并且不会导致问题。

为什么你应该保护对共享变量的写入,即使它是原子的:

如果在谓词中检查共享变量和等待条件之间发生共享变量,则可能会出现问题。请考虑以下事项:

  1. 等待线程虚假地唤醒,获取互斥锁,检查谓词并将其评估为false,因此它必须再次等待cv。

  2. 控制主题将共享变量设置为true

  3. 控制线程会发送任何人都没有收到的通知,因为没有线程在等待条件变量。

  4. 等待线程等待条件变量。由于通知已经发送,它将等到下一次虚假唤醒,或下次控制线程发送通知时。可能会无休止地等待。

  5. 从没有锁定的共享原子变量读取通常是安全的,除非它引入TOCTOU problems

    在您的情况下,您正在读取共享变量以避免不必要的锁定,然后在锁定后再次检查(在条件wait调用中)。这是一个有效的优化,称为双重检查锁定,我在这里看不到任何潜在的问题。

    您可能想要检查atomic<bool>是否无锁。否则你将拥有更多没有锁的锁。

答案 1 :(得分:1)

通常,您希望将变量原子化的事实视为与条件变量的工作方式无关。

如果与条件变量交互的所有代码都遵循在查询/修改之前锁定互斥锁的通常模式,并且与条件变量交互的代码不依赖于不与条件变量交互的代码,它将继续即使它包含原子互斥体也是正确的。

通过快速阅读您的伪代码,这似乎是正确的。但是,伪代码通常不能替代多线程代码的实际代码。

当原子读取说你可能想要的时候,只等待条件变量(和锁定互斥锁)的“优化”可能是也可能不是优化。您需要描述吞吐量。

答案 2 :(得分:0)

原子数据不需要另一个同步,它是无锁算法和数据结构的基础。

void do_pause_test_threads(SomeClass *someclass)
{
    if (!someclass->pause_test_threads)
    {
        /// your pause_test_threads might be changed here by other thread
        /// so you have to acquire mutex before checking and changing
        /// or use atomic methods - compare_exchange_weak/strong,
        /// but not all together                                                                                                                                                                                                                                                                                                                                                             
        std::lock_guard<std::mutex> lk(someclass->cv_m);
        someclass->pause_test_threads = true;
    }
}