簡體   English   中英

將std :: condition_variable與atomic一起使用<bool>

[英]Using std::condition_variable with atomic<bool>

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);
        ...
    }

我查看了你的代碼操作條件變量和原子,它似乎是正確的,不會導致問題。

為什么你應該保護對共享變量的寫入,即使它是原子的:

如果在謂詞中檢查共享變量和等待條件之間發生共享變量,則可能會出現問題。 考慮以下:

  1. 等待線程虛假地喚醒,獲取互斥鎖,檢查謂詞並將其評估為false ,因此它必須再次等待cv。

  2. 控制線程將共享變量設置為true

  3. 控制線程發送通知,任何人都沒有收到通知,因為沒有線程在等待條件變量。

  4. 等待線程等待條件變量。 由於通知已經發送,它將等到下一次虛假喚醒,或下次控制線程發送通知時。 可能會無休止地等待。

除非引入TOCTOU問題 ,否則從沒有鎖定的共享原子變量讀取通常是安全的。

在您的情況下,您正在讀取共享變量以避免不必要的鎖定,然后在鎖定后再次檢查它(在條件wait調用中)。 這是一個有效的優化,稱為雙重檢查鎖定,我在這里看不到任何潛在的問題。

您可能想檢查atomic<bool>是否無鎖。 否則你將擁有更多沒有它的鎖。

通常,您希望將變量與原子無關,而與其對條件變量的工作方式無關。

如果與條件變量交互的所有代碼都遵循在查詢/修改之前鎖定互斥鎖的通常模式,並且與條件變量交互的代碼不依賴於不與條件變量交互的代碼,那么它將繼續是正確的即使它包含原子互斥體。

從快速閱讀您的偽代碼,這似乎是正確的。 但是,偽代碼通常不能替代多線程代碼的實際代碼。

只有等待條件變量(並鎖定互斥鎖)的“優化”,當原子讀取說你可能想要可能是也可能不是優化。 您需要分析吞吐量。

原子數據不需要另一個同步,它是無鎖算法和數據結構的基礎。

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;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM