2008-08-29 68 views
253

信号量是一种常用于解决多线程问题的编程概念。我向社区提出的问题:什么是信号量?

什么是信号灯,以及如何使用它?

+9

一个布尔标志,其值取决于整数计数器是否达到其指定的上限。混淆到最大! – Sam 2011-09-19 01:02:00

回答

0

信号量是一种锁定资源的方式,以确保在执行一段代码时,只有这段代码才能访问该资源。这使得两个线程不会同时访问资源,这可能会导致问题。

+11

听起来像一个互斥体而不是信号量 – Sam 2011-09-19 00:57:52

16

@Craig:

信号量是锁定一个 资源,使得可以保证 在执行一段代码, 仅这段代码访问 该资源的方式。这使得两个线程 不会同时访问可能导致问题的资源 。

这不仅限于一个线程。信号量可以配置为允许固定数量的线程访问资源。

+2

这是一个评论,而不是一个答案。 – kaspersky 2013-05-19 17:15:40

+7

是的,但我认为在评论被添加到堆栈溢出之前我写了这个。或者我没有,不记得。这次我在评论中回答。 :-) – 2013-09-15 21:36:40

+0

当你意识到答案的尴尬时刻在2008年发布。怀旧... – 2017-10-21 02:05:41

136

迈克尔巴尔的文章Mutexes and Semaphores Demystified是一个很好的简短介绍什么使互斥和信号量不同,什么时候它们应该和不应该使用。我在这里摘录了几个关键段落。

关键是应该使用互斥锁来保护共享资源,而信号量应该用于信令。您通常不应该使用信号量来保护共享资源,也不会使用信号互斥。例如,在使用信号量来保护共享资源方面,保镖类比存在一些问题 - 您可以以这种方式使用它们,但这可能很难诊断错误。

尽管互斥锁和信号量在实现上有一些相似之处,但它们的使用方式应始终不同。

最常见的(但仍然不正确)回答顶部提出的问题是互斥体和信号量非常相似,唯一的显着区别是信号量可以计数高于1。几乎所有的工程师似乎都正确地理解互斥锁是一种二进制标志,通过确保代码的关键部分之间的相互排斥来保护共享资源。但是当被要求扩展如何使用“计数信号量”时,大多数工程师只会根据他们的信心程度不同而表达一些教科书观点的味道,这些观点用于保护若干等效资源。

...

此时一个有趣的比喻是用浴室键的主意,因为保护共享资源做 - 浴室。如果一家商店有一间浴室,那么一个密钥就足以保护该资源并防止多人同时使用它。

如果有多个浴室,可能会试图锁定他们并制作多个键 - 这与信号量被误用相似。一旦你有了钥匙,你实际上并不知道哪个浴室可用,如果沿着这条路走下去,你可能最终会使用互斥体来提供这些信息,并确保你不会占用已经占用的浴室。

信号量是保护几个本质上相同的资源的错误工具,但这是有多少人想到它并使用它。保镖类比明显不同 - 不存在几种相同类型的资源,而是有一种资源可以接受多个并发用户。我认为在这种情况下可以使用信号量,但很少有类比实际存在的现实世界情况 - 更常见的是有几种相同的类型,但仍然是单独的资源,如浴室,这些资源无法使用这条路。

...

正确使用信号灯的是信号从一个任务到另一个。每个使用它所保护的共享资源的任务都会按照该顺序执行和释放互斥锁。相比之下,使用信号量的任务不是信号就是等待 - 不是两者都有。例如,任务1可以包含用于在按下“电源”按钮时发布(即,信号或增加)特定信号量的代码,并且唤醒显示器的任务2依赖于相同的信号量。在这种情况下,一个任务是事件信号的生产者;另一个消费者。

...

这里很重要的一点是提出,互斥干扰实时操作系统在一个糟糕的方式,导致其中一个不太重要的任务可能因为之前的一个更重要的任务执行优先级反转资源共享。简而言之,当较低优先级的任务使用互斥锁来获取资源A时,会发生这种情况,然后尝试抓取B,但由于B不可用而暂停。当它在等待时,一个更高优先级的任务就会出现,并且需要A,但它已经被绑定了,并且一个甚至没有运行的进程,因为它正在等待B.有很多方法可以解决这个问题,但它通常是固定的通过改变互斥和任务管理器。在这些情况下,互斥体比二进制信号量复杂得多,在这种情况下使用信号量会导致优先级反转,因为任务管理器不知道优先级反转,并且无法采取措施纠正它。

...

互斥体和信号量之间的广泛的现代混乱的原因是历史的,因为它的日期,一路回到1974年发明的信号量(大写的“S”的,在这篇文章中)由Djikstra。在此之前,计算机科学家已知的中断安全任务同步和信号传输机制都不能有效扩展以供两项以上任务使用。 Dijkstra的革命性,安全和可扩展的Semaphore应用于临界区保护和信号传输。因此混乱开始了。然而,随着基于优先级的抢占式RTOS(例如VRTX,大约1980年)的出现,建立RMA的学术论文的发表以及由优先级反转引起的问题,后来它对于操作系统开发者而言变得显而易见,并且一份关于1990年优先权继承协议的文件,3显而易见,互斥量必须不仅仅是具有二进制计数器的信号量。

互斥:资源共享

信号灯:信号

不要使用一个对于其他没有仔细考虑的副作用。

+6

看看这个斯坦福并发PDF文件。看第8页。上面的解释将会更有意义。然后 https://see.stanford.edu/materials/icsppcs107/23-Concurrency-Examples.pdf – 2016-02-23 01:13:46

58

互斥:独家成员访问资源

信号灯:正成员对资源的访问

也就是说,一个互斥体可用于syncronize访问计数器,文件,数据库等

一个sempahore可以做同样的事情,但支持固定数量的同时呼叫者。例如,我可以将数据库调用包装在一个信号量(3)中,以便多线程应用程序最多可以同时连接3个数据库。所有尝试都会阻止,直到三个插槽中的一个打开。他们让事情像做天真的节流真的很容易。

4

信号量是一个包含自然数(即大于或等于零的整数)的对象,其上定义了两个修改操作。一个操作,V,自然增加1。另一个操作P将自然数减少1.两个活动都是原子的(即,不能与VP同时执行其他操作)。

由于自然数0不能降低,在含有0会阻塞调用过程(/线程)的执行,直到某一时刻在其数目是不再0和P可以成功旗语主叫P(和原子)执行。

正如其他答案中所述,信号量可用于将对特定资源的访问限制为最大(但可变)数量的进程。

313

将信号量想象成夜总会的保镖。俱乐部一次允许有一定数量的专用人员。如果俱乐部已满,则不允许任何人进场,但一旦有人离开另一人可能进场。

这只是一种限制特定资源消费者数量的方法。例如,限制应用程序中同时调用数据库的次数。

这是在C#中一个非常教学例子:-)

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Threading; 

namespace TheNightclub 
{ 
    public class Program 
    { 
     public static Semaphore Bouncer { get; set; } 

     public static void Main(string[] args) 
     { 
      // Create the semaphore with 3 slots, where 3 are available. 
      Bouncer = new Semaphore(3, 3); 

      // Open the nightclub. 
      OpenNightclub(); 
     } 

     public static void OpenNightclub() 
     { 
      for (int i = 1; i <= 50; i++) 
      { 
       // Let each guest enter on an own thread. 
       Thread thread = new Thread(new ParameterizedThreadStart(Guest)); 
       thread.Start(i); 
      } 
     } 

     public static void Guest(object args) 
     { 
      // Wait to enter the nightclub (a semaphore to be released). 
      Console.WriteLine("Guest {0} is waiting to entering nightclub.", args); 
      Bouncer.WaitOne();   

      // Do some dancing. 
      Console.WriteLine("Guest {0} is doing some dancing.", args); 
      Thread.Sleep(500); 

      // Let one guest out (release one semaphore). 
      Console.WriteLine("Guest {0} is leaving the nightclub.", args); 
      Bouncer.Release(1); 
     } 
    } 
} 
11

信号量也可以用作...信号灯。 例如,如果您有多个进程将数据排入队列,并且只有一个任务使用队列中的数据。如果您不希望消费任务持续轮询队列中的可用数据,则可以使用信号量。

这里信号量不是用作排斥机制,而是用作信号机制。 消费任务正在等待信号量 生产任务正在信号量上发布。

这样的耗时的任务运行时,只有当没有要出队

1

硬件或软件的标志数据。在多任务处理系统中,信号量是可变的,具有指示公共资源状态的值。需要资源的进程检查信号量以确定资源状态,然后决定如何继续。

0

这是一个老问题,但信号量的最有趣的用途之一是读/写锁,它没有被明确提及。

这些读写锁的工作原理很简单:为读者使用一个许可证,并为作者使用所有许可证。 事实上,一个r/w锁的简单实现,但需要读取(实际上两次)的元数据修改,可能会成为瓶颈,仍然明显好于互斥锁或锁。

另一个缺点是作者可以很容易地启动,除非信号量是公平的,或者写入在多个请求中获得许可,在这种情况下,他们之间需要明确的互斥。

而且read

1

所以,想象每个人都试图去洗手间并且只需按键卫生间一定数量。现在如果没有足够的按键,那个人需要等待。所以把信号量看作代表那些可供浴室使用的密钥集合(系统资源),以便不同的进程(卫浴业者)可以请求访问。

现在设想两个进程试图同时去卫生间。这不是一个好的情况,信号灯用于防止这种情况。不幸的是,信号量是一种自愿的机制和过程(我们的浴室观众)可以忽略它(即使有钥匙,有人仍然可以打开门)。

计数信号量的二进制/互斥量为&也有差异。

查看http://www.cs.columbia.edu/~jae/4118/lect/L05-ipc.html的讲义。

10

构建并发程序有两个基本概念 - 同步和互斥。我们将看到这两种类型的锁(信号量通常是一种锁定机制)如何帮助我们实现同步和互斥。

信号量是一种编程结构,通过实现同步和互斥,帮助我们实现并发。信号量有两种类型,二进制和计数。

信号量有两部分:计数器和等待访问特定资源的任务列表。信号量执行两个操作:wait(P)[这就像获取一个锁],并释放(V)[类似于释放一个锁] - 这是信号量中唯一可以执行的两个操作。在二进制信号量中,计数器在逻辑上介于0和1之间。你可以把它看作与具有两个值的锁相似:open/closed。计数信号量有多个计数值。

重要的是要理解的是,信号量计数器跟踪不必阻止任务的数量,即他们可以取得进展。只有当计数器为零时,任务才会阻塞,并将自己添加到信号量列表中。因此,如果任务无法进行,则会将任务添加到P()例程的列表中,并使用V()例程“释放”任务。

现在,了解如何使用二进制信号来解决同步和互斥 - 它们本质上是锁定是非常明显的。

ex。同步:

thread A{ 
semaphore &s; //locks/semaphores are passed by reference! think about why this is so. 
A(semaphore &s): s(s){} //constructor 
foo(){ 
... 
s.P(); 
;// some block of code B2 
... 
} 

//thread B{ 
semaphore &s; 
B(semaphore &s): s(s){} //constructor 
foo(){ 
... 
... 
// some block of code B1 
s.V(); 
.. 
} 

main(){ 
semaphore s(0); // we start the semaphore at 0 (closed) 
A a(s); 
B b(s); 
} 

在上面的例子中,B2只能在B1完成执行后才能执行。假设线程A先执行 - 进入sem.P()并等待,因为计数器为0(关闭)。线程B出现,完成B1,然后释放线程A - 然后完成B2。所以我们实现同步。

现在让我们来看看互斥与二进制信号:

thread mutual_ex{ 
semaphore &s; 
mutual_ex(semaphore &s): s(s){} //constructor 
foo(){ 
... 
s.P(); 
//critical section 
s.V(); 
... 
... 
s.P(); 
//critical section 
s.V(); 
... 

} 

main(){ 
semaphore s(1); 
mutual_ex m1(s); 
mutual_ex m2(s); 
} 

互斥很简单以及 - M1,并在同一时间M2不能进入临界区。所以每个线程都使用相同的信号量为其两个关键部分提供相互排斥。现在,是否有可能具有更高的并发性?取决于关键部分。 (想想如何使用信号量来实现互斥......提示提示:我是否只需要使用一个信号量?)

计数信号量:具有多个值的信号量。让我们看看这是什么意思 - 一个具有多个值的锁?所以打开,关闭,...嗯。用于互斥或同步的多阶段锁定有什么用处?

让我们以两个简单:用一个计数信号

同步:比方说,你有3个任务 - #1和2在要3.你会如何设计你的同步后执行?

thread t1{ 
... 
s.P(); 
//block of code B1 

thread t2{ 
... 
s.P(); 
//block of code B2 

thread t3{ 
... 
//block of code B3 
s.V(); 
s.V(); 
} 

因此,如果您的信号量开始关闭,您可以确保t1和t2块被添加到信号量列表中。然后,所有重要的t3,完成其业务,并释放t1和t2。他们释放了什么命令?取决于信号量列表的实施。可能是FIFO,可能基于某些特定的优先级等。 (注意:考虑如何安排你的P和V;如果你想以某种特定的顺序执行t1和t2,以及如果你不知道信号量的实现)

(Find out :如果V的数量大于P的数量,会发生什么?)

相互排斥使用计数信号量:我想让您为此构建自己的伪代码(让您更好地理解事物!) - 但是基本概念是这样的:counter = N的计数信号允许N个任务自由地进入临界区。这意味着你有N个任务(或线程,如果你喜欢的话)进入关键部分,但是第N + 1个任务被阻塞(进入我们最喜欢的阻塞任务列表),只有当某个V的信号量至少一次。所以信号量计数器,而不是在0和1之间摆动,现在在0和N之间,允许N个任务自由进入和退出,阻止任何人!

现在天哪,你为什么需要这样一个愚蠢的东西?互相排斥的关键不是让一个人获得资源吗? (提示提示......你并不总是只有一个驱动器在你的电脑,你......?)

要想想:是互斥的信号量仅具有计数实现?如果你有10个资源实例,并且有10个线程进入(通过计数信号量)并尝试使用第一个实例会怎么样?

2

考虑一下,出租车。出租车最多可容纳3名(后方)+2(前方)人员,包括司机。所以,信号量一次只允许一个车内有5个人。 一个互斥体只允许一个人坐在车内的座位上。 所以,互斥体是允许资源的独占访问。 信号量允许一次访问n个资源。

0

这里是关于旗语一些更有趣的知识:

信号量是一个编程架构E. W. Dijkstra算法在60年代末设计的。迪克斯特拉的模式是铁路运营:考虑一段铁路,其中有一条单一轨道,一次只允许有一列火车。

保护这条赛道是一个信号量。在进入单轨之前,列车必须等待,直到信号灯处于允许旅行的状态。当列车进入轨道时,信号灯改变状态以防止其他列车进入轨道。一列正在离开这段轨道的火车必须再次改变信号灯的状态,以允许另一列火车进入。

在计算机版本中,信号量似乎是一个简单的整数。一个线程等待进程的许可,然后通过在信号量上执行P操作发出信号。

该操作的语义是这样的,线程必须等到信号量的值为正,然后通过减去信号量的值来改变信号量的值。完成后,线程执行V操作,通过向信号量的值加1来改变信号量的值。这些操作以原子方式进行至关重要 - 它们不能被细分为在信号量之间可发生其他动作的片断。在P操作中,信号量的值在递减之前必须为正值(导致一个值保证为非负值,一个值比递减前的值小)。

在P和V操作中,算术运算都必须无干扰地进行。如果两个V操作在同一个信号量上同时执行,则净效应应该是信号量的新值比它大两倍。

由于Dijkstra是荷兰人,因此P和V的助记符意义对世界大多数人来说并不清楚。然而,为了获得真正的奖学金:P代表prolagen,一个来自proberen te verlagen的伪造词,意思是试图减少。 V代表verhogen,意思是增加。这在Dijkstra的技术笔记之一EWD 74中有讨论。

sem_wait(3RT)和sem_post(3RT)对应于Dijkstra的P和V操作。 sem_trywait(3RT)是P操作的条件形式:如果调用线程无法等待信号量减少,则调用立即返回非零值。

有两种基本的信号量:二进制信号,从未采取零或一个其他值,和计数信号量,可以采取任意非数值。二进制信号量在逻辑上就像一个互斥量。

但是,虽然它没有强制执行,但互斥锁只能由持有锁的线程解锁。没有“持有信号量的线程”的概念,所以任何线程都可以执行V(或sem_post(3RT))操作。

计数信号量和条件变量一样强大(与互斥量一起使用)。在许多情况下,使用计数信号量而不是条件变量来实现代码可能会更简单(如下几个示例所示)。

但是,当一个互斥量与条件变量一起使用时,会隐含一个包围 - 它清楚程序的哪一部分正在受到保护。对于信号量来说,情况并非一定如此,这可能被称为并发编程 - 它非常强大,但却非常容易以非结构化,不确定的方式使用。

2

在编程中,特别是在Unix系统中,信号量是一种技术,用于协调或同步多个进程竞争相同操作系统资源的活动。

信号量是操作系统存储器中指定位置的值,每个进程都可以检查并更改。根据找到的值,流程可以使用资源或意识到它已被使用,并在再次尝试之前等待一段时间。

信号可以是二进制(0或1)或可以有附加值。通常,使用信号量的进程会检查该值,然后如果使用该资源,则更改该值以反映此值,以便随后的信号量用户知道要等待。

信号量通常用于两个目的: 1)共享公用内存空间 2)共享文件访问。

信号量是进程间通信(IPC)技术之一。

C编程语言提供了一组用于管理信号量的接口或“函数”。

相关问题