计算信号量的用例

时间:2014-10-06 13:28:43

标签: multithreading operating-system semaphore

要明确:我主要是嵌入式内容,即它是C和微控制器中的某种实时内核;但实际上这个问题应该是与平台无关的。

我读过Michael Barr的好文章:Mutexes and Semaphores Demystified,以及related answer on StackOverflow。我清楚地知道二进制信号量是什么,以及互斥量是什么。那很好。

但说实话,我从来不知道,仍然无法理解,所谓的计数信号量(即最大计数> 1的信号量)是为了什么。在什么情况下我应该使用它?

很久以前,在我读过迈克尔·巴尔的上述文章之前,我已经说过“你可以使用它,就像你有一定数量的床位的酒店房间一样。床是信号量的最大数量,就像该房间的一些按键“。

这可能听起来很不错,但实际上我在编程练习中从未遇到过这样的情况(并且无法想象),Michael Barr说这种方法是错误的,他似乎是对的。

然后,在我读完这篇文章之后,我认为它可能会在我拥有某种FIFO缓冲区时使用。假设缓冲区的容量是10个元素,我们有两个任务:A(生产者)和B(消费者)。然后:

  • 信号量的最大数量应设置为10;
  • 当A想要将数据放入缓冲区时,signal是信号量。
  • 当B想要从缓冲区获取数据时,wait是信号量。

嗯,但它不起作用:

  • 如果A试图将新数据输入FIFO,但没有空间怎么办?它将如何等待这个地方:它应该在放入新数据之前调用signal(并且signal然后应该能够等到最大计数< max count)?如果是这样,信号量将在数据实际放入FIFO之前发出信号,这是错误的。
  • 信号量不足以进行正确的同步:FIFO本身也需要同步。然后,它产生了经典的TOCTTOU问题:有一段时间信号量已经发信号或等待,但FIFO尚未修改。

那么,我什么时候应该使用那个野兽,计数信号量呢?

3 个答案:

答案 0 :(得分:7)

经典'实际上,这是一个生产者 - 消费者队列。

无界队列需要一个信号量(计算队列条目)和受互斥锁保护的线程安全队列(或等效的无锁线程安全队列)。信号量初始化为零。生产者锁定互斥锁,将对象推入队列,解锁互斥锁并发信号通知信号量。消费者等待信号量,锁定互斥锁,弹出对象并解锁互斥锁。

有界队列需要两个信号量,(一个计数'计算条目,另一个'可用'计算可用空间)和一个受互斥锁保护的线程安全queue,(或等效的无锁线程安全队列)。 '计算'被初始化为零并且可以使用'到空队列中空闲的空间数。生产者等待可用',锁定互斥锁,将对象推入队列,解锁互斥锁并发出信号'计数。消费者等待'计数,锁定互斥锁,弹出对象,解锁互斥锁并发出可用的信号'。

这是信号量的经典用法,并且永远存在,(好吧,自Dijkstra以来,无论如何:)。它已经被尝试了数十亿次,并且适用于任何数量的生产者/消费者。

没有TOCTTOU问题,没有角球,没有比赛。

'互斥'功能可以由另一个信号量提供,初始化为1.这允许两个信号量'无限的,三个信号量'有限的实现。

答案 1 :(得分:6)

  

我认为可能会在我拥有某种FIFO缓冲区时使用它。假设缓冲区的容量是10个元素,我们有两个任务:A(生产者)和B(消费者)。然后:

     
      
  • 信号量的最大数量应设置为10;
  •   
  • 当A想要将数据放入缓冲区时,它会向信号量发出信号。
  •   
  • 当B想要从缓冲区获取数据时,它会等待信号量。
  •   

这不是信号量在生产者 - 消费者场景中使用的方式。标准解决方案是使用两个计数信号量,一个用于空插槽(初始化为可用插槽数),另一个用于填充插槽(初始化为0)。

生产者尝试分配空槽以放入项目,因此它们以wait开头 - 分配给空槽的信号量。消费者试图“分配”(掌握)填充的插槽,因此他们从wait开始 - 分配给已填充的插槽的信号量。

完成他们的工作后,他们都发出信号通知另一个信号量,因为他们分别将空格从空白变为填充,从填充变为空白。

标准解决方案:

semaphore mutex  = 1;
semaphore filled = 0;
semaphore empty  = SIZE;

producer() {
    while ( true) {
        item = produceItem();
        wait(empty);

        wait(mutex);
        putItemIntoBuffer( item);
        signal(mutex);

        signal(filled);
    }
}

consumer() {
    while ( true) {
        wait( filled);

        wait( mutex);
        item = removeItemFromBuffer();
        signal( mutex);

        signal( empty);
        consumeItem( item);
    }
}

我认为计算信号量在这种情况下很有用。


另一个,也许更简单的例子可能是使用计数信号量来避免在餐饮哲学家场景中出现死锁。由于只有当所有哲学家同时坐下并挑选他们的(比如说)左叉时才能发生僵局,因此可以避免死锁,因为他们不允许所有人同时进入餐厅。这可以通过计数信号量(enter)初始化为少于哲学家的数量来实现。

一位哲学家的协议随后成为:

wait( enter)

wait( left_fork)
wait( right_fork)
eat()
signal( left_fork)
signal( right_fork)

signal( enter)

这确保所有哲学家不能同时进入餐厅。

答案 2 :(得分:0)

一些比较流行的使用信号量的用例是-

  • 限制JDBC连接池中的连接数。
  • 限制网络连接。
  • 限制并发访问磁盘等资源。