需要互斥锁来保护条件变量

时间:2012-05-18 07:08:58

标签: pthreads mutex condition-variable

据说需要Mutex来保护条件变量。

此处的引用是否声明为pthread_cond_t的实际条件变量 OR
普通共享变量count,其值决定信令并等待 ?

4 个答案:

答案 0 :(得分:6)

  

这里引用了声明为pthread_cond_t的实际条件变量或正常的共享变量count,其值决定了信令并等待?

参考两者。

互斥锁使得可以检查共享变量(问题中的count),如果该变量的值不符合所需条件,则在pthread_cond_wait()内执行等待将以原子方式发生在该支票上。

使用互斥锁解决的问题是您有两个需要原子的独立操作:

  1. 检查count
  2. 的条件
  3. 如果条件尚未达到,则等待pthread_cond_wait()的insode。
  4. pthread_cond_signal()没有'持久' - 如果pthread_cond_t对象上没有线程等待,则信号什么也不做。因此,如果没有互斥体使上面列出的两个操作彼此原子化,那么您可能会发现自己处于以下情况:

    • 线程A想要在count非零
    • 后执行某些操作
    • 线程B会在它递增count时发出信号(将count设置为零以外的其他值)

      1. 线程“A”检查count并发现它为零
      2. 在“A”调用pthread_cond_wait()之前,线程“B”出现并将count增加到1并调用pthread_cond_signal()。该调用实际上没有任何后果,因为“A”还没有等待pthread_cond_t对象。
      3. “A”调用pthread_cond_wait(),但由于不记住条件变量信号,它将在此时阻塞并等待已经过去的信号。

    互斥锁(只要所有线程都遵循规则)使得第1项和第3项之间不能发生第2项。线程“B”有机会增加count的唯一方法在A查看count之前或在“A”已经在等待信号之前。

答案 1 :(得分:3)

条件变量必须始终与互斥锁相关联,以避免线程准备在条件变量上等待的竞争条件,而另一个线程在第一个线程实际等待它之前发出信号。

更多信息here

一些示例:

线程1(等待条件)

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

线程2(表示条件)

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

正如您在上面所见,互斥锁保护变量'i',这是导致该情况的原因。当我们看到条件不满足时,我们进入一个条件wait,它隐式释放互斥锁,从而允许执行信号的线程获取互斥锁并处理'i'并避免竞争条件。

现在,根据你的问题,如果信令线程首先发出信号,它应该在这之前获取互斥锁,否则第一个线程可能只是检查条件并看到它没有得到满足并且可能会等待条件等待并且因为第二个线程已经发出信号,所以没有人会在那之后发出信号,并且第一个线程将永远等待。因此,从这个意义上说,互斥量适用于条件和条件。条件变量。

答案 2 :(得分:1)

根据pthreads文档,互斥锁未被分离的原因是,通过组合它们可以显着提高性能,并且他们希望由于常见的竞争条件,如果您不使用互斥锁,它会使用互斥锁。无论如何,几乎总是要做。

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

  

互斥锁和条件变量的特征

     

有人建议互斥量的获取和释放   与条件等待脱钩。这被拒绝了,因为它是   实际上,操作的组合性质促进了实时性   实现。这些实现可以原子地移动a   条件变量和互斥体之间的高优先级线程   对呼叫者透明的方式。这可以防止额外的   上下文切换并提供更多确定性的互斥锁获取   当等待线程发出信号时。因此,公平和优先   问题可以由调度规则直接处理。   此外,当前条件等待操作与现有条件匹配   实践。

答案 3 :(得分:0)

我认为更好的用例可能有助于更好地解释条件变量及其相关的互斥锁。

我使用posix条件变量来实现所谓的Barrier Sync。基本上,我在一个应用程序中使用它,我有15个(数据平面)线程,它们都做同样的事情,我希望它们都等到所有数据平面完成初始化。一旦他们完成了(内部)数据平面初始化,他们就可以开始处理数据了。

这是代码。注意我从Boost复制了算法,因为我不能在这个特定的应用程序中使用模板:

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

请注意,进入barrierSync()方法的每个线程都会锁定互斥锁,从而使互斥锁与调用pthread_cond_wait()pthread_mutex_unlock()原子之间的所有内容都成为可能。另请注意,pthread_cond_wait()中的互斥锁 已释放/解锁 ,如上所述here。在此链接中,如果您在未先锁定互斥锁的情况下调用pthread_cond_wait(),则还会提到行为未定义

如果pthread_cond_wait()没有释放互斥锁,那么所有线程都会在pthread_mutex_lock()方法的开头阻止对barrierSync()的调用,并且不可能减少barrierCounter_变量(也不是操纵相关的变量)以原子方式(也不是以线程安全的方式)来知道有多少线程调用了barrierSync()

因此,总结所有这些,与条件变量相关联的互斥锁用于保护条件变量本身,而是用于使与条件相关联的逻辑({{ 1}}等)原子和线程安全。当线程阻塞等待条件变为真时,它们实际上阻止相关互斥锁上的条件变量 。拨打barrierCounter_的电话会取消阻止。

Here是另一个与pthread_cond_broadcast/signal()pthread_cond_broadcast()相关的资源,可供其他参考。