何时使用信号量以及何时使用条件变量?
7 回答
锁用于互斥。如果您想确保一段代码是原子的,请在其周围加锁。理论上您可以使用二进制信号量来执行此操作,但这是一种特殊情况。
信号量和条件变量建立在锁提供的互斥之上,用于提供对共享资源的同步访问。它们可用于类似目的。
条件变量通常用于在等待资源可用时避免忙等待(在检查条件时重复循环)。例如,如果您有一个线程(或多个线程)在队列为空之前无法继续前进,那么忙碌的等待方法就是执行以下操作:
//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()
orbroadcast()
方法来唤醒所有正在等待的线程)。
当我有一个或多个线程等待一个特定条件(例如队列为空)时,我通常使用这样的同步变量。
信号量可以类似地使用,但我认为当您拥有一个基于某些可用事物的整数个可用和不可用的共享资源时,它们会更好地使用。信号量适用于生产者/消费者的情况,即生产者分配资源而消费者消耗资源。
想想你是否有汽水自动售货机。只有一台汽水机,它是共享资源。您有一个线程是负责保持机器库存的供应商(生产者)和 N 个线程是想要从机器中取出苏打水的买家(消费者)。机器中的汽水数量是驱动我们信号量的整数值。
每个来到汽水机的买家(消费者)线程都会调用 semaphoredown()
方法来拿汽水。这将从机器中获取苏打水并将可用苏打水的数量减 1。如果有苏打水可用,代码将继续运行通过down()
语句而不会出现问题。如果没有苏打水可用,线程将在这里休眠,等待苏打水再次可用的通知(当机器中有更多苏打水时)。
供应商(生产者)线程本质上是在等待汽水机变空。当从机器中取出最后一杯汽水时,供应商会收到通知(并且一个或多个消费者可能正在等待取出汽水)。供应商将使用 semaphoreup()
方法为汽水机补货,每次可用的汽水数量都会增加,因此等待的消费者线程会收到有更多汽水可用的通知。
同步变量的wait()
andsignal()
方法往往隐藏在信号量的down()
andup()
操作中。
当然,这两种选择之间存在重叠。在许多情况下,信号量或条件变量(或一组条件变量)都可以满足您的目的。信号量和条件变量都与用于维护互斥的锁对象相关联,但随后它们在锁之上提供了额外的功能,用于同步线程执行。主要由您决定哪一个对您的情况最有意义。
这不一定是最技术性的描述,但这在我的脑海中是有意义的。
让我们揭示引擎盖下的内容。
条件变量本质上是一个等待队列,它支持阻塞等待和唤醒操作,即你可以将一个线程放入等待队列并将其状态设置为BLOCK,并从中取出一个线程并将其状态设置为READY。
请注意,要使用条件变量,还需要另外两个元素:
- 条件(通常通过检查标志或计数器来实现)
- 保护条件的互斥锁
然后协议变为,
- 获取互斥锁
- 检查条件
- 如果条件为真,则阻止并释放互斥锁,否则释放互斥锁
信号量本质上是一个计数器 + 一个互斥体 + 一个等待队列。它可以在没有外部依赖的情况下按原样使用。您可以将其用作互斥锁或条件变量。
因此,信号量可以被视为比条件变量更复杂的结构,而后者更轻量级和灵活。
信号量可用于实现对变量的独占访问,但它们旨在用于同步。另一方面,互斥体具有与互斥严格相关的语义:只有锁定资源的进程才允许解锁它。
不幸的是,您无法使用互斥锁实现同步,这就是我们有条件变量的原因。另请注意,使用条件变量,您可以使用广播解锁在同一时刻解锁所有等待线程。这不能用信号量来完成。
信号量和条件变量非常相似,主要用于相同的目的。但是,有一些细微的差异可以使一个更可取。例如,要实现屏障同步,您将无法使用信号量。但条件变量是理想的。
屏障同步是指您希望所有线程都等到每个人都到达线程函数中的某个部分。这可以通过拥有一个静态变量来实现,该变量最初是每个线程在到达该障碍时递减的总线程的值。这意味着我们希望每个线程都处于休眠状态,直到最后一个线程到达。信号量会完全相反!使用信号量,每个线程将继续运行,最后一个线程(将信号量值设置为 0)将进入睡眠状态。
另一方面,条件变量是理想的。当每个线程到达屏障时,我们检查我们的静态计数器是否为零。如果没有,我们使用条件变量等待函数将线程设置为休眠。当最后一个线程到达屏障时,计数器值将减为零,最后一个线程将调用条件变量信号函数,该函数将唤醒所有其他线程!
我在监视器同步下归档条件变量。我通常将信号量和监视器视为两种不同的同步方式。两者在本质上保留多少状态数据以及如何对代码进行建模方面存在差异 - 但实际上没有任何问题可以由一个解决,而另一个解决不了。
我倾向于对监视器形式进行编码;在我工作的大多数语言中,这归结为互斥体、条件变量和一些支持状态变量。但是信号量也可以完成这项工作。
和mutex
继承conditional variables
自semaphore
。
- 对于
mutex
,semaphore
使用两种状态:0、1 - 对于
condition variables
使用semaphore
计数器。
它们就像语法糖
条件变量 + 互斥量 == 信号量