Mutex are needed to protect the Condition Variable

2019-03-31 20:13发布

问题:

As it is said that Mutex are needed to protect the Condition Variables.

Is the reference here to the actual condition variable declared as pthread_cond_t
OR
A normal shared variable count whose values decide the signaling and wait.
?

回答1:

is the reference here to the actual condition variable declared as pthread_cond_t or a normal shared variable count whose values decide the signaling and wait?

The reference is to both.

The mutex makes it so that the shared variable (count in your question) can be checked and if the value of that variable doesn't meet the desired condition the wait that is performed inside pthread_cond_wait() will occur atomically with respect to that check.

The problem being solved with the mutex is that you have two separate operations that need to be atomic:

  1. check the condition of count
  2. wait insode of pthread_cond_wait() if the condition isn't met yet.

A pthread_cond_signal() doesn't 'persist' - if there are no threads waiting on the pthread_cond_t object, a signal does nothing. So if there wasn't a mutex making the two operations listed above atomic with respect to one another you could find yourself in the following situation:

  • Thread A wants to do something once count is non-zero
  • Thread B will signal when it increments count (which will set count to something other than zero)

    1. thread "A" checks count and finds that it's zero
    2. before "A" gets to call pthread_cond_wait(), thread "B" comes along and increments count to 1 and calls pthread_cond_signal(). That call actually does nothing of consequence since "A" isn't waiting on the pthread_cond_t object yet.
    3. "A" calls pthread_cond_wait(), but since condition variable signals aren't remembered, it will block at this point and wait for the signal that has already come and gone.

The mutex (as long as all threads are following the rules) makes it so that item #2 cannot occur between items 1 and 3. The only way that thread "B" will get a chance to increment count is either before A looks at count or after "A" is already waiting for the signal.



回答2:

A condition variable must always be associated with a mutex, to avoid the race condition where a thread prepares to wait on a condition variable and another thread signals the condition just before the first thread actually waits on it.

More info here

Some Sample:

Thread 1 (Waits for the condition)

pthread_mutex_lock(cond_mutex);
while(i<5)
{
 pthread_cond_wait(cond, cond_mutex);
}
pthread_mutex_unlock(cond_mutex);

Thread 2 (Signals the condition)

pthread_mutex_lock(cond_mutex);
 i++;
if(i>=5)
{
  pthread_cond_signal(cond);
}
pthread_mutex_unlock(cond_mutex);

As you can see in the same above, the mutex protects the variable 'i' which is the cause of the condition. When we see that the condition is not met, we go into a condition wait, which implicitly releases the mutex and thereby allowing the thread doing the signalling to acquire the mutex and work on 'i' and avoid race condition.

Now, as per your question, if the signalling thread signals first, it should have acquired the mutex before doing so, else the first thread might simply check the condition and see that it is not being met and might go for condition wait and since the second thread has already signalled it, no one will signal it there after and the first thread will keep waiting forever.So, in this sense, the mutex is for both the condition & the conditional variable.



回答3:

Per the pthreads docs the reason that the mutex was not separated is that there is a significant performance improvement by combining them and they expect that because of common race conditions if you don't use a mutex, it's almost always going to be done anyway.

https://linux.die.net/man/3/pthread_cond_wait​

Features of Mutexes and Condition Variables

It had been suggested that the mutex acquisition and release be decoupled from condition wait. This was rejected because it is the combined nature of the operation that, in fact, facilitates realtime implementations. Those implementations can atomically move a high-priority thread between the condition variable and the mutex in a manner that is transparent to the caller. This can prevent extra context switches and provide more deterministic acquisition of a mutex when the waiting thread is signaled. Thus, fairness and priority issues can be dealt with directly by the scheduling discipline. Furthermore, the current condition wait operation matches existing practice.



回答4:

I thought that a better use-case might help better explain conditional variables and their associated mutex.

I use posix conditional variables to implement what is called a Barrier Sync. Basically, I use it in an app where I have 15 (data plane) threads that all do the same thing, and I want them all to wait until all data planes have completed their initialization. Once they have all finished their (internal) data plane initialization, then they can start processing data.

Here is the code. Notice I copied the algorithm from Boost since I couldnt use templates in this particular application:

void LinuxPlatformManager::barrierSync()
{
  // Algorithm taken from boost::barrier

  // In the class constructor, the variables are initialized as follows:
  //   barrierGeneration_ = 0;
  //   barrierCounter_ = numCores_; // numCores_ is 15
  //   barrierThreshold_ = numCores_;


  // Locking the mutex here synchronizes all condVar logic manipulation
  // from this point until the point where either pthread_cond_wait() or 
  // pthread_cond_broadcast() is called below
  pthread_mutex_lock(&barrierMutex_);

  int gen = barrierGeneration_;

  if(--barrierCounter_ == 0)
  {
    // The last thread to call barrierSync() enters here,
    // meaning they have all called barrierSync()
    barrierGeneration_++;
    barrierCounter_ = barrierThreshold_;

    // broadcast is the same as signal, but it signals ALL waiting threads
    pthread_cond_broadcast(&barrierCond_);
  }

  while(gen == barrierGeneration_)
  {
    // All but the last thread to call this method enter here
    // This call is blocking, not on the mutex, but on the condVar
    // this call actually releases the mutex
    pthread_cond_wait(&barrierCond_, &barrierMutex_);
  }

  pthread_mutex_unlock(&barrierMutex_);
}

Notice that every thread that enters the barrierSync() method locks the mutex, which makes everything between the mutex lock and the call to either pthread_cond_wait() or pthread_mutex_unlock() atomic. Also notice that the mutex is released/unlocked in pthread_cond_wait() as mentioned here. In this link it also mentions that the behavior is undefined if you call pthread_cond_wait() without having first locked the mutex.

If pthread_cond_wait() did not release the mutex lock, then all threads would block on the call to pthread_mutex_lock() at the beginning of the barrierSync() method, and it wouldnt be possible to decrease the barrierCounter_ variables (nor manipulate related vars) atomically (nor in a thread safe manner) to know how many threads have called barrierSync()

So to summarize all of this, the mutex associated with the Conditional Variable is not used to protect the Conditional Variable itself, but rather it is used to make the logic associated with the condition (barrierCounter_, etc) atomic and thread-safe. When the threads block waiting for the condition to become true, they are actually blocking on the Conditional Variable, not on the associated mutex. And a call to pthread_cond_broadcast/signal() will unblock them.

Here is another resource related to pthread_cond_broadcast() and pthread_cond_signal() for an additional reference.