据说需要互斥锁来保护条件变量。
这里是对声明为pthread_cond_t
OR的实际条件变量的引用
一个正常的共享变量count
,其值决定信号和等待。
?
据说需要互斥锁来保护条件变量。
这里是对声明为pthread_cond_t
OR的实际条件变量的引用
一个正常的共享变量count
,其值决定信号和等待。
?
这里是对声明为 pthread_cond_t 的实际条件变量的引用还是一个正常的共享变量计数,其值决定信号和等待?
两者皆有参考。
互斥锁使得count
可以检查共享变量(在您的问题中),并且如果该变量的值不满足所需条件,则内部执行的等待pthread_cond_wait()
将相对于该检查以原子方式发生。
互斥锁解决的问题是您有两个需要原子的独立操作:
count
pthread_cond_wait()
如果条件尚未满足,请在里面等待。Apthread_cond_signal()
不会“持续” - 如果没有线程在等待pthread_cond_t
对象,则信号什么也不做。因此,如果没有互斥体使上面列出的两个操作彼此原子化,您可能会发现自己处于以下情况:
count
非零线程 B 将在其递增时发出信号count
(将设置count
为非零值)
count
并发现它为零pthread_cond_wait()
,线程“B”出现并递增count
到 1 并调用pthread_cond_signal()
。该调用实际上没有任何影响,因为“A”还没有等待pthread_cond_t
对象。pthread_cond_wait()
,但由于不记住条件变量信号,它会在此时阻塞并等待已经到来和消失的信号。互斥锁(只要所有线程都遵循规则)使得项目#2 不会出现在项目 1 和项目 3 之间。线程“B”有机会增加的唯一方法count
是在 A 查看之前count
或之后“A”已经在等待信号。
条件变量必须始终与互斥体相关联,以避免线程准备等待条件变量而另一个线程在第一个线程实际等待它之前发出条件信号的竞争条件。
更多信息在这里
一些样本:
线程 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”,这是导致该条件的原因。当我们看到条件不满足时,我们进入条件等待,这会隐式释放互斥锁,从而允许执行信号的线程获取互斥锁并在“i”上工作并避免竞争条件。
现在,根据您的问题,如果信号线程首先发出信号,它应该在这样做之前获取互斥锁,否则第一个线程可能只是检查条件并看到它没有被满足,并且可能会等待条件等待,因为第二个线程已经发出信号,之后没有人会发出信号,第一个线程将永远等待。所以,从这个意义上说,互斥锁同时适用于条件和条件变量。
根据 pthreads 文档,互斥锁未分离的原因是通过组合它们可以显着提高性能,并且他们希望由于常见的竞争条件,如果您不使用互斥锁,无论如何它几乎总是会完成。
https://linux.die.net/man/3/pthread_cond_wait </p>
互斥锁和条件变量的特点
有人建议将互斥锁的获取和释放与条件等待分离。这被拒绝了,因为实际上是操作的组合性质促进了实时实施。这些实现可以以对调用者透明的方式在条件变量和互斥锁之间自动移动高优先级线程。这可以防止额外的上下文切换,并在等待线程发出信号时提供对互斥锁的更具确定性的获取。因此,调度规则可以直接处理公平性和优先级问题。此外,当前条件等待操作符合现有实践。
我认为更好的用例可能有助于更好地解释条件变量及其关联的互斥锁。
我使用 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()
pthread_cond_wait()
如果pthread_cond_wait()
没有释放互斥锁,那么所有线程都会pthread_mutex_lock()
在方法开始时阻塞调用 to barrierSync()
,并且不可能以barrierCounter_
原子方式(也不以线程安全的方式)减少变量(也不操作相关的变量)来知道调用了多少线程barrierSync()
所以总而言之,与条件变量关联的互斥锁不是用于保护条件变量本身,而是用于使与条件(barrierCounter_
等)关联的逻辑具有原子性和线程安全性。当线程阻塞等待条件变为真时,它们实际上是阻塞在条件变量上,而不是在关联的互斥体上。并且调用pthread_cond_broadcast/signal()
将取消阻止它们。
这是另一个相关的资源pthread_cond_broadcast()
并pthread_cond_signal()
提供额外的参考。