条件变量与信号量

Posted

技术标签:

【中文标题】条件变量与信号量【英文标题】:Conditional Variable vs Semaphore 【发布时间】:2011-03-31 14:15:37 【问题描述】:

何时使用信号量,何时使用条件变量?

【问题讨论】:

相关信息也可以在链接中找到***.com/questions/4039899/… 【参考方案1】:

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

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

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

//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() 语句而不会出现问题。如果没有苏打水可用,线程将在这里休眠,等待苏打水再次可用的通知(当机器中有更多苏打水时)。

供应商(生产者)线程本质上是在等待汽水机变空。当从机器中取出最后一杯汽水时,供应商会收到通知(并且一个或多个消费者可能正在等待取出汽水)。供应商将使用信号量 up() 方法为汽水机补货,每次可用的汽水数量都会增加,因此等待的消费者线程会收到通知有更多汽水可用。

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

当然,这两个选择之间存在重叠。在许多情况下,信号量或条件变量(或一组条件变量)都可以满足您的目的。信号量和条件变量都与它们用来维护互斥的锁对象相关联,但是它们在锁之上提供了额外的功能,用于同步线程执行。主要由您决定哪一个对您的情况最有意义。

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

【讨论】:

很好的答案,我想从其他答案中添加:信号量用于控制执行的线程数。将有固定的资源集。每次线程拥有资源时,资源计数都会减少。当信号量计数达到 0 时,则不允许其他线程获取资源。线程被阻塞,直到拥有资源的其他线程释放。简而言之,主要区别在于一次允许多少个线程获取资源?互斥体——它的一个。信号量 -- 它的 DEFINED_COUNT,(与信号量数量一样多) 只是为了详细说明为什么会有这个while循环而不是一个简单的if:叫做spurios wakeup的东西。引用this wikipedia article:“其中一个原因是虚假唤醒;也就是说,即使没有线程发出条件变量信号,线程也可能从等待状态中唤醒” @VladislavsBurakovs 好点!我认为这对于广播唤醒的线程多于可用资源的情况也很有帮助(例如,广播唤醒了 3 个线程,但队列中只有 2 个项目)。 我希望我会投票支持您回答,直到队列已满;)完美答案。这段代码可以帮助找出信号量csc.villanova.edu/~mdamian/threads/PC.htm @VladislavsBurakovs 澄清一下,对于刚刚唤醒的线程(导致虚假唤醒),条件可能仍然为假的原因是,在线程有机会再次检查条件,其中一些其他计划线程使该条件为假。这是我知道虚假唤醒的原因之一,不知道是否还有更多。【参考方案2】:

让我们揭开幕后的真相。

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

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

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

协议就变成了,

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

信号量本质上是一个计数器 + 一个互斥体 + 一个等待队列。 它可以按原样使用,无需外部依赖。您可以将其用作互斥体或条件变量。

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

【讨论】:

mutex 可以看成是一个条件变量,它的条件就是是否被持有。 关于the protocol的描述有误!【参考方案3】:

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

很遗憾,您无法使用互斥锁实现同步,这就是我们有条件变量的原因。另请注意,使用条件变量,您可以使用广播解锁在同一时刻解锁所有等待线程。信号量无法做到这一点。

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

条件变量对于实现障碍也不是很好。特别是,在递减所述计数器和使自己在条件变量上休眠之间存在竞争条件。所以还需要一个互斥锁。每个线程必须首先获取互斥锁,然后递减并检查计数器,然后在原子释放互斥锁时让自己在条件变量上休眠。当稍后所有线程唤醒时,它们都需要重新获取该 Mutex,但一次只能这样做一个线程。因此,如果操作系统库提供了 Barrier 原语,那么就使用它!【参考方案5】:

我在监视器同步下归档条件变量。我通常将信号量和监视器视为两种不同的同步方式。两者在本质上保留了多少状态数据以及您希望如何对代码进行建模方面存在差异 - 但实际上没有任何问题可以由一个而不是另一个来解决。

我倾向于编写监控表单;在我工作的大多数语言中,这归结为互斥体、条件变量和一些支持状态变量。但是信号量也可以完成这项工作。

【讨论】:

如果您解释什么是“监控表单”,这将是一个更好的答案。【参考方案6】:

mutexconditional variables 继承自 semaphore

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

它们就像语法糖

【讨论】:

在 C++ 标准库中,它们都是区对象,都使用平台特定的 API 实现。当然,信号量会解除阻塞发出信号的次数,条件变量可能会发出多次信号但仅解除阻塞一次。这就是 wair 将互斥体作为参数的原因。【参考方案7】:

conditionalVar + mutex == 信号量

【讨论】:

以上是关于条件变量与信号量的主要内容,如果未能解决你的问题,请参考以下文章

信号量与条件变量的区别

linux条件变量使用和与信号量的区别

随机信号分析学习—多维随机变量与条件随机变量

条件变量与互斥量

信号量互斥锁和条件变量的区别

信号量,互斥锁,读写锁和条件变量的区别