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

Eri*_*pää 8 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);
        ...
    }
Run Code Online (Sandbox Code Playgroud)

Rev*_*lot 11

我查看了你的代码操作条件变量和原子,它似乎是正确的,不会导致问题.

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

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

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

  2. 控制线程集共享变量true.

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

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

除非引入TOCTOU问题,否则从没有锁定的共享原子变量读取通常是安全的.

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

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