2014-10-06 39 views
0

要清楚:我主要是嵌入的东西,即它是C和微控制器中的某种实时内核;但实际上这个问题应该是平台无关的。 Mutexes and Semaphores Demystified,以及本related answer on StackOverflow计数信号量的使用案例

我由迈克尔·巴尔读好文章。我清楚地明白什么是二进制信号量,什么是互斥量。那很棒。

但说实话,我从来不知道,还是不能懂,还有什么所谓的计数信号量(即,最大信号计数> 1)可以。我应该在什么情况下使用它?很久以前,在我读过迈克尔巴尔的前述文章之前,我已经告诉过类似于“”的东西,你可以在有的时候使用它,比如有一定数量的床的旅馆房间。是信号量的最大计数,就像该房间的一些键号“。

这可能听起来好听,但实际上我从来没有在我的编程实践中有过这样的情况(和无法想象任何)和迈克尔·巴尔说,这种做法是错误的,他似乎是正确的。

然后,我读过的文章后,我认为当我有,说出来可能会被使用,某种FIFO缓存。假设缓冲区的容量是10个元素,我们有两个任务:A(生产者)和B(消费者)。然后:

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

好,但它不工作:

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

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

回答

6

“经典”的例子是,确实是,生产者 - 消费者队列。

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

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

这是一个信号量的经典用途,并一直存在,因为永远,(自从Dijkstra,无论如何:)。它已经尝试了数十亿次,并且对于任何数量的生产者/消费者都可以正常工作。

没有TOCTTOU问题,没有角落案例,没有比赛。

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

+0

好吧,从生产者P1完成等待“可用”并锁定互斥锁的那一刻起,所以,可能有其他生产者P2中断了这个序列(并首先锁定了互斥体)。然后,P1锁定互斥锁,但队列已满,P1必须解锁互斥锁并再次等待“可用”。这真的很好吗?哇,只有一个数据队列的三个对象。我使用的实时内核(TNeoKernel:https://bitbucket.org/dfrank/tneokernel)提供了提供原子等待和放置数据以及原子等待和获取数据操作的数据队列。 – 2014-10-07 08:56:52

+0

好吧,从生产者P1完成等待“可用”并锁定互斥锁的那一刻起,因此,在这种情况下,可能有其他生产者P2中断了该序列(并且先锁定互斥体)',因为这两个线程都从'可用'信号量获得了单元,所以在该队列中必须有两个空闲空间,所以两者线程可以推送他们的对象而不会溢出队列。您建议的实时内核 - 它允许与任意数量的生产者/消费者进行阻塞,有界的队列? – 2014-10-08 02:32:37

+0

此外,你可以用'无锁'队列替换'简单队列和互斥'。只要无锁队列类对于多个生产者和消费者是无条件安全的。 – 2014-10-08 02:37:43

4

我想这可能可能会使用,当我有,比如说,某种FIFO缓冲区。假设缓冲区的容量是10个元素,我们有两个任务:A(生产者)和B(消费者)。然后:

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

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

生产者尝试分配空插槽放入项目,因此它们以分配给空插槽的信号量开始wait -ing。消费者尝试“分配”(获得)已填充的插槽,因此他们从分配给已填充插槽的信号量开始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) 

这确保了所有哲学家不能在餐厅同时。