条件变量与信号量

什么时候使用信号量,什么时候使用条件变量?

105973 次浏览

锁是用来装互斥锁的。当您想要确保一段代码是原子的时候,在它周围放置一个锁。理论上,您可以使用一个二进制信号量来完成这项工作,但这是一个特例。

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

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

//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()操作中。

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

这不一定是最专业的描述,但这就是它在我脑海中的意义所在。

信号量可以用来实现对变量的独占访问,但是它们应该用于同步。另一方面,互斥对象有一个严格与互斥锁相关的语义,只有锁定资源的进程才被允许解锁它。

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

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

我倾向于使用监视器的形式编写代码; 在大多数语言中,我使用的都是互斥锁、条件变量和一些后备状态变量。但信号灯也能起作用。

让我们看看引擎盖下面是什么。

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

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

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

协议就变成了,

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

信号量本质上是一个计数器 + 一个互斥锁 + 一个等待队列。而且它可以在没有外部依赖的情况下使用。您可以将其用作互斥对象或条件变量。

因此,信号量可以看作是一个比条件变量更复杂的结构,而条件变量是更轻量级和灵活的。

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

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

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

mutexconditional variables是从 semaphore继承来的。

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

它们就像句法糖

ContionalVar + mutex = = 信号量

信号量需要预先知道初始化的计数。对条件变量没有这样的要求。