何时使用信号量(semaphore),何时使用条件变量(conditional variable)?
何时使用信号量(semaphore),何时使用条件变量(conditional variable)?
锁被用于互斥。当您想要确保一段代码是原子性的时候,请在其周围放置一个锁。理论上,您可以使用二进制信号量来实现此目的,但那是一种特殊情况。
信号量和条件变量基于锁提供的互斥性,并用于提供对共享资源的同步访问。它们可用于类似的目的。
条件变量通常用于避免忙等待(重复循环检查条件),同时等待资源变得可用。例如,如果您有一个线程(或多个线程)无法继续前进,直到队列为空,则忙等待的方法将只做以下事情:
//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()
操作中。
当然,两种选择之间存在重叠。许多情况下,信号量或条件变量(或一组条件变量)都可以满足您的需求。信号量和条件变量都与它们用于维护互斥性的锁定对象相关联,但是它们在锁定的基础上提供了额外的功能以同步线程执行。在大多数情况下,由您来确定哪种方法最适合您的情况。
这可能不是最技术性的描述,但在我的脑海中这就是它有意义的方式。
syncVar
需要一个独立可访问的锁,即为什么线程不能简单地执行syncVar.wait()
,并在条件满足且线程具有独占访问权限时返回。当完成后,它可以syncVar().release()
。 - einpoklum让我们揭开其本质。
条件变量本质上是一个等待队列,它支持阻塞等待和唤醒操作。也就是说,您可以将线程放入等待队列中,并将其状态设置为BLOCK,也可以从中获取线程并将其状态设置为READY。
请注意,要使用条件变量,需要两个其他元素:
然后协议变成了:
信号量本质上是一个计数器 + 互斥锁 + 等待队列。它可以独立使用而不需要外部依赖。您可以将其用作互斥锁或条件变量。
因此,信号量可以被视为比条件变量更复杂的结构,而后者更轻巧和灵活。
协议
的描述是错误的! - John信号量可以用来实现对变量的独占访问,但是它们的意图是用于同步。而互斥锁的语义严格与互斥有关:只有锁定资源的进程可以解锁它。
不幸的是,互斥锁无法实现同步,这就是为什么我们需要条件变量。另外请注意,使用条件变量时,您可以通过广播解锁所有等待线程,使它们在同一时刻被解锁。这不能通过信号量实现。
信号量和条件变量非常相似,通常用于相同的目的。然而,有一些微小的区别可能会使一个更可取。例如,要实现屏障同步,您将无法使用信号量。但条件变量是理想的。
屏障同步是当您想让所有线程等待,直到每个人都到达线程函数中的某个部分时。这可以通过具有静态变量来实现,该变量最初为总线程数的值,并在每个线程到达该屏障时递减。这意味着我们希望每个线程在最后一个线程到达之前都处于休眠状态。信号量会恰好相反!使用信号量,每个线程都将继续运行,而最后一个线程(它将将信号量值设置为0)将进入睡眠状态。
另一方面,条件变量是理想的。当每个线程到达屏障时,我们检查静态计数器是否为零。如果不是,则使用条件变量等待功能将线程设置为睡眠状态。当最后一个线程到达屏障时,计数器值将递减为零,此最后一个线程将调用条件变量信号功能,这将唤醒所有其他线程!
信号量需要在初始化时提前知道计数。条件变量没有这样的要求。
我在监视器同步下对文件条件变量进行分类。我通常将信号量和监视器视为两种不同的同步风格。两者之间存在差异,包括固有状态数据的保存量以及如何模拟代码 - 但是实际上,没有一种方法可以解决问题而另一种方法不能解决。
我倾向于编写监视器形式的代码; 在我工作的大多数语言中,这归结为互斥锁,条件变量和一些后端状态变量。但是信号量也能完成任务。
mutex
和条件变量
都是从信号量
继承而来。
mutex
,信号量
使用两个状态:0、1条件变量
,信号量
使用计数器。它们就像语法糖一样。
条件变量 + 互斥锁 == 信号量