“data race” (not really) after notifying condition

2019-07-24 20:55发布

问题:

My problem is illustrated below. Notification on std::condition_variable can be missed by Thread II; Thread III can acquire lock before it and change the condition.

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

Is there any way to deal with this.?

(Application). Consider something like 'alarm' class with 'wait( state )', 'start' and 'cancel' methods. It has associated thread, that is a "waiter" thread. All of the methods can be called on single object. While cancel and start can be synced with additional mutex, it cannot be done for wait for obvious reasons. It can be workarounded by simply storing state of some ulong counter before each wait and then comparing stored and current - if they differ ( incremented by start or cancel), then state was switched, notification occured.

回答1:

Notification on std::condition_variable can be missed by Thread II;

Condition variable notifications can be missed if there is no thread waiting for it when a notification is emitted. The code must rather wait for the change of the state. Condition variable notifications are a hint that the state may have changed and must be re-evaluated.

You have 2 threads racing to change state to A and B correspondingly. There is no ordering constraint between them, so that state can be either A or B. In the latter case the thread waiting for state == A blocks forever.