71

何时应该使用信号量,何时应该使用条件变量(CondVar)?条件变量与信号量

+0

相关信息也可以在链接中找到http://stackoverflow.com/questions/4039899/when-should-we-use-mutex-and-when-should-we-use-semaphore – 2015-10-20 16:57:35

回答

145

锁用于相互排斥。当你想要确保一段代码是原子的时候,请锁定它。理论上你可以使用二进制信号来做到这一点,但这是一个特例。

信号量和条件变量建立在互斥的顶部由锁提供,并且用于提供对共享资源的同步访问。它们可以用于类似的目的。

条件变量通常用于避免忙等待(循环反复,同时检查的条件),同时等待资源变得可用。举例来说,如果你有一个线程(或多个线程),无法继续前进,直到队列为空,则忙等待的方法是仅仅做这样的事情:

//pseudocode 
while(!queue.empty()) 
{ 
    sleep(1); 
} 

这样做的问题是,你通过让这个线程重复检查条件来浪费处理器时间。为什么不相反有一个同步变量,可以发信号告诉线程资源可用?

//pseudocode 
syncVar.lock.acquire(); 

while(!queue.empty()) 
{ 
    syncVar.wait(); 
} 

//do stuff with queue 

syncVar.lock.release(); 

大概,你会有一个线程在其他地方把事情拉出队列。当队列为空时,它可以调用syncVar.signal()唤醒正在熟睡坐在上syncVar.wait()随机线程(或通常也有一个signalAll()broadcast()方法来唤醒所有正在等待的线程)。

我一般使用同步变量这样,当我有一个或多个线程等待单个特定条件(例如,用于队列是空的)。

信号灯同样可以使用,但我认为他们是更好的使用,当你有一个共享资源,可以根据可用的东西某一整数可用和不可用。信号量对于生产者分配资源和消费者消费它们的生产者/消费者情况是有利的。

想想你是否有一台苏打水自动售货机。只有一台苏打水机,它是一个共享资源。你有一个线程是供应商(生产者)负责保持机器的库存,并且N个线程是想要从机器中获得苏打水的买家(消费者)。机器中苏打水的数量是驱动我们信号量的整数值。

自带的汽水机

每个买家(消费者)线程调用信号down()方法采取苏打水。这将从机器中汲取苏打水,并将可用苏打水的数量减1。如果有苏打水可用,代码将继续运行,通过down()声明没有问题。如果没有苏打水可用,线程将在此处睡眠,等待苏打水何时再次可用(当机器中有更多苏打水时)。

供应商(生产商)线程将基本上等待苏打机器为空。当最后一次汽水被从机器中取出时,供应商会得到通知(并且一个或多个消费者可能正在等待喝苏打水)。供应商将通过信号量方法来补充苏打水机器,每次可用的苏打水数量都会增加,从而等待的消费者线程会得到通知,提供更多的苏打水。

同步变量的wait()signal()方法倾向于隐藏在信号量的操作down()up()内。

当然这两种选择之间有重叠。信号量或条件变量(或一组条件变量)都可以满足您的需求。信号量和条件变量都与它们用于维护互斥的锁对象相关联,但它们在锁的顶部提供了用于同步线程执行的额外功能。这主要取决于你找出哪一个对你的情况最有意义。

这不一定是最具技术性的描述,但这就是它在我脑海中的意义。

+6

哇!优秀的答案。 – user373215 2010-08-18 18:45:50

+0

这真的很有帮助。谢谢布伦特。 – ashu 2014-03-04 04:19:09

+7

好的答案,我想从其他答案中加入:Semaphore用于控制执行的线程数。将会有一组固定的资源。每当线程拥有相同的资源时,资源计数就会减少。当信号计数达到0时,则不允许其他线程获取资源。线程被阻塞,直到拥有资源释放的其他线程。 总之,主要区别是允许多少个线程同时获取资源? 互斥量 - 其中一个。信号量 - 它的DEFINED_COUNT(与信号计数一样多) – berkay 2014-08-17 06:47:09

13

信号量可用于实现对变量的独占访问,但它们旨在用于同步。另一方面,互斥体具有与互斥严格相关的语义:只有锁定资源的过程才允许解锁。

不幸的是,你不能用互斥实现同步,这就是为什么我们有条件变量。还要注意,通过使用条件变量,您可以通过使用广播解锁在同一时刻解锁所有正在等待的线程。这不能用信号量来完成。

1

我在监视器同步下文件条件变量。我通常将信号量和监视器看作两种不同的同步方式。就状态数据固有的数量以及你想如何建模代码而言,两者之间存在差异 - 但实际上没有任何问题可以由一个解决,而不是由另一个解决。

我倾向于向监视器表单编码;在大多数语言中,我的工作涉及到互斥体,条件变量和一些支持状态变量。但信号量也可以完成这项工作。

+2

如果你解释了什么是“监视器窗体”,这将是一个更好的答案。 – 2014-05-05 16:43:47

21

让我们来揭示隐藏的内容。

条件变量本质上是一个等待队列,支持阻塞等待和唤醒操作,即可以把一个线程进入等待队列,并设置其状态为BLOCK,并从中获得一个线程出来,将其状态设置为READY。

注意,使用条件变量,需要另外两个元素:

  • 条件
  • 保护条件
互斥(典型地通过检查标志或计数器实现)

然后将协议变为,

  1. 获取互斥
  2. 检查条件
  3. 块和释放互斥体,如果条件为真,否则释放互斥

信号灯基本上是一个计数器+互斥+一个等待队列。它可以被使用,因为它没有外部依赖。您可以将其用作互斥或作为条件变量。

因此,信号量可以被视为比条件变量更复杂的结构,而后者更加轻便和灵活。

+0

关于这些原始本质的伟大写作! – 2016-06-06 19:17:32

3

信号量和条件变量非常相似,主要用于相同的目的。但是,有一些细微的差异可能会使人更喜欢。例如,要实现障碍同步,您将无法使用信号量。但条件变量是理想的。

屏障同步是指您希望所有线程都等待,直到每个人都到达线程函数中的某个部分。这可以通过使用一个静态变量来实现,该变量最初是每个线程到达该屏障时递减的总线程数的值。这意味着我们希望每个线程都睡觉,直到最后一个线程到达。信号量完全相反!有一个信号量,每个线程都会继续运行,最后一个线程(将信号量值设置为0)将进入休眠状态。

另一方面,条件变量是理想的。当每个线程到达屏障时,我们检查我们的静态计数器是否为零。如果不是,我们将线程设置为使用条件变量等待函数进入休眠状态。当最后一个线程到达屏障时,计数器值将递减到零,最后一个线程将调用条件变量信号函数,该函数将唤醒所有其他线程!