通知条件变量并解锁关联互斥锁后的“数据竞争”(不是真的)

Posted

技术标签:

【中文标题】通知条件变量并解锁关联互斥锁后的“数据竞争”(不是真的)【英文标题】:"data race" (not really) after notifying condition variable and unlocking associated mutex 【发布时间】:2017-06-19 05:06:25 【问题描述】:

我的问题如下图所示。 std::condition_variable 上的通知可能会被线程 II 错过;线程 III 可以在它之前获取锁并改变条件。

/*  
     Thread I          Thread II                 Thread III
_____________________________________________________________
| lock M        | wait for notify         | wait for M    |   |
| cond = stateA |                         |               |   |
| notify        | unblock                 |               |   |
| unlock M      | wait for M              | lock M        |   |
|               |                         | cond = stateB |   |
|               | lock M                  | unblock M     |   |
|               | check if cond == stateA |               |   |   
|               |      ...                |               |  \ / t
                                                              *       
    */

#include <iostream>
#include <condition_variable>
#include <chrono>
#include <thread>
#include <limits>
#include <mutex>

int main() 

    using namespace std::chrono ;
    std::mutex mtx ;
    std::condition_variable cv ;

    enum EState
    
        A , B
     state = B ; // mtx

    // possible workaround
    using count_t = unsigned long long ;
    count_t set_A_state_count = 0 ; // mtx
    // 18,446,744,073,709,551,615 - number, that may cause missing ;
    // ( if Thread III function would get executed exactly this number of times
    // before Thread II acquire the mutex )   
    // believe it is not relevant for present days.

    auto ThreadI = [ &set_A_state_count , &cv ,
                     &mtx , &state ] () 
     
        std::lock_guard< std::mutex > lock  mtx  ;
        state = A  ;
        ++ set_A_state_count ;
        cv.notify_one() ;
     ;

    auto ThreadIII = [ &cv , &mtx , &state ] () 
     
        std::lock_guard< std::mutex > lock  mtx  ;
        state = B ;
     ;


    std::unique_lock< std::mutex > lock  mtx  ;

    std::thread thI ( ThreadI ) , thIII ( ThreadIII ) ;

    const auto saved_count = set_A_state_count ;

    if ( state != A ) 
       while( saved_count == set_A_state_count )  // pred ()
        // releasing and waiting 
           cv.wait( lock ) ;
        // acquiring - place where ThreadIII can outrun main thread ( ThreadII on the inlustration )
       
    

    count_t times = ( saved_count < set_A_state_count ) ?
                                   set_A_state_count - saved_count 
                                 : std::numeric_limits< count_t >::max() - 
                                      set_A_state_count + saved_count ;

    std::cout << "state was changed to A " << times << " times." << std::flush ; 
    thI.join() ;
    thIII.join() ;

    return 0;

有什么办法可以解决这个问题吗?

(应用程序)。考虑类似带有“wait(state)”、“start”和“cancel”方法的“alarm”类。它有关联的线程,即“服务员”线程。所有方法都可以在单个对象上调用。虽然取消和启动可以与额外的互斥锁同步,但由于明显的原因不能等待。它可以通过在每次等待之前简单地存储一些 ulong 计数器的状态然后比较存储的和当前的状态来解决 - 如果它们不同(通过启动或取消递增),然后状态被切换,通知发生。

【问题讨论】:

第二个condition_variable?尽管您的绘图很有帮助,但您可能需要考虑 editing 您的问题以包含 minimal reproducible example 我觉得如果线程 3 必须等待线程 1 和线程 2,那么线程 1 应该通知线程 2,线程 2 应该通知线程 3。你所拥有的是试图保护太多太少了。 从我可以看到你的线程#3 改变了 state 而不管任何其他线程可能在做什么。我能看到让线程#3 与其他线程合作的唯一方法是像处理其他线程一样放入等待和状态测试代码。 【参考方案1】:

std::condition_variable 上的通知可能会被线程 II 错过;

如果在发出通知时没有线程等待它,则可能会错过条件变量通知。代码必须等待状态的改变。条件变量通知提示状态可能已更改,必须重新评估。

您有 2 个线程竞相将 state 相应地更改为 AB。它们之间没有排序约束,因此state 可以是AB。在后一种情况下,等待state == A 的线程将永远阻塞。

【讨论】:

以上是关于通知条件变量并解锁关联互斥锁后的“数据竞争”(不是真的)的主要内容,如果未能解决你的问题,请参考以下文章

如果我需要在锁定互斥锁后解锁它,我该如何返回一个值?

Linux系统编程5_条件变量与互斥锁

Linux系统编程5_条件变量与互斥锁

信号量互斥锁和条件变量的区别

C++线程间的互斥和通信

C++线程间的互斥和通信