条件变量与信号量


Answers:


207

锁用于互斥。当您想要确保一段代码是原子的时,请在其周围加一个锁。从理论上讲,您可以使用二进制信号量来执行此操作,但这是一种特殊情况。

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

通常使用条件变量来避免在等待资源可用时的繁忙等待(在检查条件时反复循环)。例如,如果您有一个(或多个)线程在队列为空之前无法继续运行,那么繁忙的等待方法将是执行以下操作:

//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()or broadcast()方法来唤醒所有正在等待的线程)。

当我有一个或多个线程在单个特定条件下等待时(例如,队列为空),我通常使用这样的同步变量。

信号量可以类似地使用,但是我认为当您拥有一个共享资源时,基于一些可用事物的整数,它们可以更好地使用。信号量适用于生产者/消费者的情况,生产者正在分配资源而消费者正在消耗它们。

考虑一下您是否有汽水自动售货机。只有一台汽水机,这是一种共享资源。您有一个线程是供应商(生产者),负责保持机器的库存,有N个线程是买家(消费者),他们希望从机器中取出苏打水。机器中的苏打水数量是将驱动信号量的整数值。

苏打水机器上的每个购买者(消费者)线程都会调用信号量down()方法来获取苏打水。这将从机器中获取苏打水,并将可用苏打水的数量减少1。如果有苏打水可用,则代码将继续运行,直到通过down()语句而没有问题。如果没有可用的苏打水,线程将在此处休眠,等待再次提供苏打水时通知您(当机器中有更多苏打水时)。

供应商(生产者)线程实际上将在等待苏打水机器为空。当从机器中取出最后的苏打水时,供应商会收到通知(一个或多个消费者可能正在等待将苏打水取出)。供应商将使用信号量up()方法对苏打水机器进行补货,每次都会增加苏打水的可用数量,从而等待的消费者线程将收到更多苏打水可用的通知。

同步变量的wait()signal()方法往往隐藏在信号量的down()up()操作中。

当然,这两个选择之间存在重叠。在许多情况下,信号量或条件变量(或条件变量集)都可以满足您的目的。信号量和条件变量都与用于保持互斥的锁定对象相关联,但随后它们在锁定之上提供了用于同步线程执行的额外功能。由您自己决定哪种情况最适合您的情况。

那不一定是最技术性的描述,但这就是我的脑海。


9
很好的答案,我想从其他答案中补充:信号量用于控制执行线程的数量。将有一组固定的资源。每当线程拥有相同资源时,资源计数将减少。当信号量计数达到0时,则不允许其他线程获取资源。线程将被阻塞,直到拥有资源的其他线程释放为止。简而言之,主要区别是允许有多少个线程一次获取资源?互斥锁-它是一个。信号灯-其DEFINED_COUNT,(与信号灯数量一样多)
berkay

10
只是为了详细说明为什么会有while循环而不是简单的if:称为spuriosakeup的东西。引用这篇维基百科文章“其原因之一是虚假的唤醒;也就是说,即使没有线程发出条件变量的信号,线程也可能从等待状态中唤醒”
Vladislavs Burakovs 2014年

3
@VladislavsBurakovs好点!我认为这对于广播唤醒的线程多于可用资源的情况也很有帮助(例如,广播唤醒3个线程,但是队列中只有2个项目)。
布伦特编写代码

希望我能赞成您的回答,直到队列已满;)完美答案。这段代码有助于找出信号量csc.villanova.edu/~mdamian/threads/PC.htm
Mohamad-Jaafar NEHME

3
@VladislavsBurakovs为了澄清一点,对于刚刚醒来的线程来说,条件可能仍然是错误的(导致虚假唤醒)的原因是,在线程有机会检查条件之前,可能已经进行了上下文切换同样,其他一些预定线程使该条件为假。这是我知道虚假唤醒的原因之一,不知道是否还有更多。
马克斯

52

让我们来揭开背后的秘密。

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

请注意,要使用条件变量,还需要两个其他元素:

  • 条件(通常通过检查标志或计数器来实现)
  • 保护条件的互斥量

协议就变成了

  1. 获取互斥
  2. 检查条件
  3. 如果条件为真,则阻止并释放互斥锁,否则释放互斥锁

信号量实际上是一个计数器+一个互斥体+一个等待队列。而且可以直接使用而无需外部依赖。您可以将其用作互斥量或条件变量。

因此,与条件变量相比,信号量可以被视为更复杂的结构,而条件变量则更为轻巧和灵活。


互斥锁可以看作是一个条件变量,它的条件是是否保留。
宏杰李

18

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

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


9

信号量和条件变量非常相似,并且大多数用于相同目的。但是,有一些细微的差异可能会使其更可取。例如,要实现屏障同步,您将无法使用信号量,但是条件变量是理想的选择。

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

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


1

我在监视器同步下归档条件变量。我通常将信号灯和监视器视为两种不同的同步样式。在固有地保留多少状态数据和如何对代码建模方面,两者之间存在差异-但实际上并没有一个可以解决的问题,而另一个则无法解决。

我倾向于编写监视器形式的代码;在我使用的大多数语言中,归结为互斥体,条件变量和一些后备状态变量。但是信号量也可以完成这项工作。


2
如果您解释什么是“监视表格”,这将是一个更好的答案。
史蒂文·卢

0

将在mutexconditional variables从继承semaphore

  • 对于mutexsemaphore使用两种状态:0、1
  • 对于condition variablessemaphore 使用计数器。

他们就像语法糖


在C ++ std库中,它们都是区域对象,都使用平台特定的API来实现。当然,信号量将取消阻塞发出信号的次数,条件变量可能会多次发出信号,但只能解除阻塞一次。这就是为什么wair将互斥锁作为参数。
多伦
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.