什么时候应该使用互斥对象,什么时候应该使用信号量

我们什么时候应该使用互斥对象,什么时候应该使用信号量?

138175 次浏览

互斥对象是一个互斥锁对象,相似是一个信号量,但是一次只允许一个锁,其所有权限制可能比信号量更严格。

它可以被认为等价于一个普通的计数信号量(计数为1) ,并且要求它只能由锁定 it(a)的同一个线程释放。

另一方面,信号量具有任意的计数,并且可以被那么多锁并发锁定。而且它可能不需要由声明它的同一个线程释放它(但是,如果不需要,您必须仔细跟踪谁当前对它负责,就像分配的内存一样)。

因此,如果你有一个资源的数量(比如三个磁带驱动器) ,你可以使用一个信号量。注意,这并不能告诉你你有哪些磁带驱动器,只是告诉你你有一个特定的数字。

同样使用信号量,单个锁可以锁定一个资源的多个实例,例如磁带到磁带的复制。如果您有一个资源(比如您不希望损坏的内存位置) ,则互斥锁更合适。

同等业务包括:

Counting semaphore          Mutual exclusion semaphore
--------------------------  --------------------------
Claim/decrease (P)                  Lock
Release/increase (V)                Unlock

旁白: 如果你想知道用来声明和释放信号量的奇怪字母(PV) ,那是因为发明者是荷兰人。用那种语言说:

  • Probeer te verlagen: 意思是尽量降低;
  • 费尔霍根: 意味着增加。

(a) ... 或者它可以被认为是完全不同于信号量的东西,考虑到它们几乎总是不同的用途,信号量可能更安全。

如前所述,计数为1的信号量与“二进制”信号量相同,后者与互斥量相同。

我所看到的计数大于1的信号量主要是生产者/消费者情况,在这种情况下,您有一个固定大小的队列。

那你就有两个信号灯了。第一个信号量最初设置为队列中的项目数,第二个信号量设置为0。生产者对第一个信号量执行 P 操作,然后添加到队列中。然后在第二个上做 V 运算。使用者对第二个信号量执行 P 操作,从队列中删除,然后对第一个信号量执行 V 操作。

这样,只要生产者填满队列,就会阻塞生产者; 只要队列为空,就会阻塞使用者。

虽然@opaxDiablo 的回答是完全正确的,但我想指出的是,两者的使用场景是完全不同的。互斥体用于保护部分代码不被并发运行,信号量用于一个线程向另一个线程发出运行的信号。

/* Task 1 */
pthread_mutex_lock(mutex_thing);
// Safely use shared resource
pthread_mutex_unlock(mutex_thing);






/* Task 2 */
pthread_mutex_lock(mutex_thing);
// Safely use shared resource
pthread_mutex_unlock(mutex_thing); // unlock mutex

信号灯的情况则不同:

/* Task 1 - Producer */
sema_post(&sem);   // Send the signal


/* Task 2 - Consumer */
sema_wait(&sem);   // Wait for signal

有关进一步说明,请参阅 http://www.netrino.com/node/202

参见“厕所的例子”-http://pheatt.emporia.edu/courses/2010/cs557f10/hand07/Mutex%20vs_%20Semaphore.htm:

互斥:

是马桶的钥匙。一个人可以有钥匙-占用厕所-在时间。完成后,该人将(释放)队列中的下一个人的密钥。

官方说法是: “互斥对象通常用于序列化对一部分可重入代码的访问,这些代码不能由多个线程并发执行。互斥对象只允许一个线程进入受控区域,迫使其他试图访问该区域的线程等待,直到第一个线程从该区域退出。” 参考文献: Symbian 开发者库

(互斥量实际上是一个值为1的信号量。)

信号灯:

是免费的一模一样的卫生间钥匙的数量。例如,假设我们有四个带有相同锁和钥匙的厕所。信号量计数——钥匙的计数——在开始时被设置为4(所有四个厕所都是免费的) ,然后计数值随着人们的进入而减少。如果所有厕所都满了。没有空闲键了,信号量计数为0。现在,等式。一个人离开厕所,信号灯增加到1(一个免费的钥匙) ,并给下一个人在队列中。

官方说法是: “信号量将共享资源的同时用户数限制到最大值。线程可以请求访问资源(递减信号量) ,并且可以发出信号表示它们已经完成使用资源(递增信号量)。” 参考文献: Symbian 开发者库

理解互斥量 不是是一个包含计数1的信号量是非常重要的!

这就是为什么会有像二进制信号量这样的东西(它们实际上是计数为1的信号量)。

互斥和二进制信号量的区别在于所有权原则:

互斥锁由任务获取,因此也必须由同一任务释放。 这使得使用二进制信号量修复一些问题成为可能(意外发布、递归死锁和优先转置)。

警告: 我写了“使之成为可能”,如果这些问题得到解决,以及如何解决取决于操作系统的实现。

因为互斥锁必须由同一个任务释放,所以对于任务的同步来说不是很好。但是,如果与条件变量组合,就可以得到构建各种 IPC 原语的非常强大的构建块。

因此,我的建议是: 如果您得到了干净实现的互斥锁和条件变量(如 POSIX pthread) ,那么使用这些。

只有当信号量完全适合你要解决的问题时才使用信号量,不要尝试构建其他原语(例如,信号量中的 rw 锁,使用互斥锁和条件变量)

互斥对象和信号量之间有很多误解。到目前为止,我找到的最好的解释是在这篇由三部分组成的文章中:

互斥与信号量-第1部分: 信号量

互斥与信号量-第2部分: 互斥

互斥与信号量-第3部分(最后一部分) : 互斥锁问题

下面是我如何记住什么时候使用什么-

信号灯: 当你(线程)想睡眠时使用一个信号灯,直到其他线程告诉你醒来。信号量‘ down’发生在一个线程(生产者) ,信号量‘ up’(对于同一个信号量)发生在另一个线程(消费者) 例如: 在生产者消费者问题中,生产者希望休眠到至少有一个缓冲槽空为止——只有使用者线程才能知道缓冲槽是空的。

互斥: 当您(线程)想要执行不应该由任何其他线程同时执行的代码时,请使用互斥锁。互斥锁“向下”发生在一个线程中,互斥锁“向上”必须的发生在同一个线程中。 例如: 如果您正在从全局链表中删除一个节点,您不希望另一个线程在您删除该节点时使用指针。当您获取一个互斥锁并忙于删除一个节点时,如果另一个线程试图获取同一个互斥锁,它将处于休眠状态,直到您释放该互斥锁。

旋锁: 当您确实想使用互斥锁但线程不允许睡眠时,请使用自旋锁。 操作系统内核的 interrupt handler 永远不能睡觉。如果它这样做,系统将冻结/崩溃。如果你需要从 interrupt handler 插入一个节点到全局共享链表中,获取一个自旋锁-插入节点-释放自旋锁。

我不想听起来很滑稽,但我情不自禁。

您的问题应该是互斥量和信号量之间的区别是什么? 更准确的问题应该是,互斥量和信号量之间的关系是什么

(我本想加上这个问题,但我百分之百肯定,某个过分热心的版主会在没有理解差异和关系之间的区别的情况下,将其作为重复内容关闭。)

在对象术语中,我们可以观察到:

1信号量包含互斥量

2互斥不是信号量,信号量也不是互斥。

有一些信号量的作用就好像它们是互斥对象,称为二进制信号量,但它们是该死的非互斥对象。

有一个称为 Signaling 的特殊成分(posx 使用条件变量作为该名称) ,它是用互斥对象创建信号量所必需的。 可以把它当作一个通知来源。如果两个或多个线程订阅了相同的通知源,那么可以将它们的消息发送到 ONE 或 ALL,以唤醒它们。

可能存在一个或多个与信号量相关联的计数器,这些信号量由互斥对象保护。对于信号量最简单的场景是,有一个计数器可以是0或1。

这就是混乱像季风雨一样涌来的地方。

计数器可以是0或1的信号量不是互斥量。

Mutex 有两个状态(0,1)和一个所有权(任务)。 信号量有一个互斥量,一些计数器和一个条件变量。

现在,运用你的想象力,每一种计数器和信号的组合使用都可以形成一种信号量。

  1. 单个计数器,值为0或1,当值为1时发出信号,然后解锁等待信号 = = 二进制信号量的一个家伙

  2. 值为0到 N 的单个计数器,当值小于 N 时发出信号,当值为 N = = 计数信号量时锁定/等待

  3. 值为0到 N 的单个计数器,当值为 N 时发出信号,当值小于 N = = 屏障信号量时锁定/等待(如果他们不调用它,那么他们应该调用它)

现在回到你的问题,什么时候用什么。(OR 相当正确的问题版本。3什么时候使用互斥和什么时候使用二进制信号量,因为没有比较非二进制信号量。) 使用互斥体时 1. 您需要一个自定义行为,它不是由二进制信号量提供的,比如自旋锁、快速锁或递归锁。 您通常可以使用属性定制互斥对象,但是定制信号量只不过是编写新的信号量。 2. 你想要轻量级的或者更快的原语

使用信号量,当您想要的正是由它提供的。

如果您不理解二进制信号量的实现提供了什么,那么 IMHO,使用互斥锁。

最后读一本书,而不是仅仅依靠所以。

我认为问题应该是互斥量和二进制信号量之间的区别。

Mutex = 这是一种所有权锁机制,只有获得锁的线程才能释放锁。

它更像是一种信号机制,任何其他高优先级的线程都可以发出信号并获取锁。

互斥量是信号量的一种特殊情况。一个信号量允许多个线程进入关键部分。在创建信号量时,您定义在关键部分如何允许线程。当然,您的代码必须能够处理对此关键部分的多次访问。

互斥是为了保护共享资源。
Semaphore 负责分派线程

互斥:
想象一下,有一些票可以卖。我们可以模拟许多人同时买票的情况: 每个人都是买票的线程。显然,我们需要使用互斥对象来保护票证,因为它是共享资源。


信号灯:
假设我们需要进行如下计算: < br >

c = a + b;

另外,我们需要一个函数 geta()来计算 a,一个函数 getb()来计算 b,一个函数 getc()来计算 c = a + b

显然,我们不能做的 c = a + b除非 geta()getb()已经完成。
如果这三个函数是三个线程,我们需要分派这三个线程

int a, b, c;
void geta()
{
a = calculatea();
semaphore_increase();
}


void getb()
{
b = calculateb();
semaphore_increase();
}


void getc()
{
semaphore_decrease();
semaphore_decrease();
c = a + b;
}


t1 = thread_create(geta);
t2 = thread_create(getb);
t3 = thread_create(getc);
thread_join(t3);

在信号量的帮助下,上面的代码可以确保在 t1t2完成它们的工作之前,t3不会完成它的工作。

总之,信号量是使线程按逻辑顺序执行,而互斥量是保护共享资源。
所以即使有些人总是说互斥量是一个初始值为1的特殊信号量,它们也不是一回事。你也可以这样说,但是请注意它们在不同的情况下使用。即使你能做到这一点,也不要一个接一个地替换。

以上所有的答案质量都很好,但是这个只是用来记忆的。互斥这个名字来源于 < em > 相互排斥 ,因此你会认为一个互斥锁是两个互斥锁之间的锁,就像一次只有一个,如果我拥有它,你只能在我释放它之后才能拥有它。

二进制信号量和互斥是不同的。从操作系统的角度来看,二进制信号量和计数信号量的实现方式是相同的,二进制信号量的值可以是0或1。

Mutex -> 只能用于一个且仅用于互斥锁关键代码段的目的。

信号量 -> 可以用来解决各种各样的问题。二进制信号量可以用来发送信号,也可以解决互斥锁问题。初始化为 0时解决了 发信号问题,初始化为 1时解决了 互斥锁问题。

当资源数量较多且需要同步时,可以使用计数信号量。

在我的博客中,我详细讨论了这些话题。

Https://designpatterns-oo-cplusplus.blogspot.com/2015/07/synchronization-primitives-mutex-and.html

我发现“ Peer Stritzinger”的答案是正确的。

我想在他的回答中引用 David R Butenhof出版的 Programming with POSIX Threads一书中的一句话。在第三章第52页,作者写道(重点是我的) :

如果调用线程已经锁定了互斥锁,则不能锁定该互斥锁。尝试这样做的结果可能是错误返回(EDEADLK) ,也可能是自死锁,不幸的线程会永远等待。无法解锁已解锁或被其他线程锁定的互斥锁。锁定的互斥锁由锁定它们的线程所拥有。如果需要“无主”锁,请使用信号量。第6.6.6节讨论信号量)

考虑到这一点,下面的代码说明了使用大小为1的信号量替代互斥量的危险性。

sem = Semaphore(1)
counter = 0 // shared variable
----


Thread 1


for (i in 1..100):
sem.lock()
++counter
sem.unlock()
----


Thread 2


for (i in 1..100):
sem.lock()
++counter
sem.unlock()
----


Thread 3


sem.unlock()
thread.sleep(1.sec)
sem.lock()


如果只针对线程1和线程2,计数器的最终值应该是200。但是,如果错误地将信号量引用泄露给另一个线程并调用 unlock,那么您就不会得到互斥锁。 对于互斥对象,这种行为根据定义是不可能的。