我们啥时候应该使用互斥锁,啥时候应该使用信号量

Posted

技术标签:

【中文标题】我们啥时候应该使用互斥锁,啥时候应该使用信号量【英文标题】:When should we use mutex and when should we use semaphore我们什么时候应该使用互斥锁,什么时候应该使用信号量 【发布时间】:2011-05-01 16:38:13 【问题描述】:

什么时候应该使用互斥体,什么时候应该使用信号量?

【问题讨论】:

What is mutex and semaphore in Java ? What is the main difference ? 的可能重复项 【参考方案1】:

这是我记得什么时候使用什么 -

信号量: 当您(线程)想要睡觉时使用信号量,直到其他线程告诉您醒来。信号量“向下”发生在一个线程(生产者)中,信号量“向上”(对于相同的信号量)发生在另一个线程(消费者)中 例如:在生产者-消费者问题中,生产者想要休眠直到至少一个缓冲槽为空——只有消费者线程才能知道缓冲槽何时为空。

互斥: 当您(线程)想要执行不应由任何其他线程同时执行的代码时,请使用互斥锁。 Mutex 'down' 发生在一个线程中,mutex 'up' 必须 稍后发生在同一个线程中。 例如:如果您要从全局链表中删除一个节点,您不希望另一个线程在您删除该节点时使用指针。当你获取一个互斥体并忙于删除一个节点时,如果另一个线程试图获取相同的互斥体,它将进入休眠状态,直到你释放互斥体。

自旋锁: 当您真的想使用互斥锁但不允许您的线程休眠时,请使用自旋锁。 例如:操作系统内核中的中断处理程序绝不能休眠。如果是这样,系统将冻结/崩溃。如果需要从中断处理程序中插入一个节点到全局共享链表,获取一个自旋锁-插入节点-释放自旋锁。

【讨论】:

补充:信号量和互斥量是提供同步的两种方式。信号量,可能与信号量更相关(例如生产者和消费者问题场景),而互斥量可能与一次允许访问一个更相关(访问共享资源的多个请求,但一次只授予一个)。 [好文章:geeksforgeeks.org/mutex-vs-semaphore/] 只有一个可能 nitpick,互斥锁真正保护资源,而不是代码。可以在广泛分散的代码段中访问这些资源,因此,只要所有这些段使用相同的互斥体,一切都应该没问题。您的答案(对我而言)的方式是互斥锁仅保护 一个 代码部分。【参考方案2】:

互斥体是一种互斥对象,类似于信号量,但一次只允许一个储物柜,其所有权限制可能比信号量更严格。

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

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

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

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

等价的操作是:

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

旁白:如果您曾经对用于声明和释放信号量的奇怪字母(PV)感到好奇,那是因为发明者是荷兰人。用那种语言:

Probeer te verlagen:表示尝试降低; Verhogen:增加的意思。

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

【讨论】:

从概念上讲,二元信号量互斥体,它相当于一个计数的普通信号量。概念的实现可能存在差异,例如效率或资源的所有权(可以由声明者以外的其他人发布,我不同意顺便说一句 - 资源应该只可由声明它的线程释放)。 另一个潜在的实现差异是递归互斥锁。因为只有一个资源,所以单个线程可能被允许多次锁定它(只要它也释放它多次)。对于多实例资源,这并不容易,因为您可能不知道线程是想要再次声明 另一个 实例还是 same 实例。 他们解决了一个特定的问题。他们解决的问题是那些完全了解互斥锁的人,这一事实绝不应贬低解决方案:-) 互斥量与二进制信号量完全不同。抱歉,这个定义是错误的 @ToolmakerSteve,我不确定你是否理解我的意图。我说过 mutex 就像一个信号量,计数为 1,并且声明线程是释放线程的限制。我并不认为 semaphore 有这个限制。我会尝试清理答案以更好地区分。【参考方案3】:

了解互斥锁不是计数为 1 的信号量非常重要!

这就是出现二进制信号量(实际上是计数为 1 的信号量)之类的东西的原因。

Mutex 和 Binary-Semaphore 的区别在于所有权原则:

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

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

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

所以我的建议是:如果您有干净利落地实现的互斥锁和条件变量(例如 POSIX pthreads),请使用这些。

仅当信号量完全适合您要解决的问题时才使用信号量,不要尝试构建其他原语(例如,rw-locks out of semaphores,使用互斥锁和条件变量)

互斥量和信号量之间有很多误解。到目前为止,我发现的最佳解释在这篇 3 部分文章中:

Mutex vs. Semaphores – Part 1: Semaphores

Mutex vs. Semaphores – Part 2: The Mutex

Mutex vs. Semaphores – Part 3 (final part): Mutual Exclusion Problems

【讨论】:

该站点的网址包含时髦的字符,因此无法正常工作......我正在处理它 链接已失效。答案没有解释二进制信号量和互斥量的规范有什么区别。 “所有权原则”是关于如何使用同步原语,因此它不属于规范。投反对票。 @beroal 我已对此进行了编辑,并且链接已更新。等到更新被接受并享受阅读它们......【参考方案4】:

虽然@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

【讨论】:

你是对的。即使您使用计数为 1 的信号量,与使用互斥锁相比,您也暗示了您正在做的事情。 我不确定我是否同意这一点,尽管我并没有不同意那么强烈以至于我会否决你:-) 你说信号量的使用模式是通知线程,但这正是互斥锁在有另一个线程等待它时所做的事情,以及当sema_wait 中没有线程时信号量 :-) 在我看来,它们是两者关于资源和传递给其他线程的通知都是保护的副作用(非常重要,性能方面)。 You say that the usage pattern of semaphores is to notify threads 关于通知线程的一点。您可以安全地从信号处理程序 (pubs.opengroup.org/onlinepubs/009695399/functions/…) 调用 sem_post,但不建议从信号处理程序 (manpages.ubuntu.com/manpages/lucid/man3/…) 调用 pthread_mutex_lockpthread_mutex_unlock @paxdiablo:这个互斥二进制信号量之间的一个主要区别是维护引用计数。互斥锁或者您可以说任何条件互斥锁不维护任何与锁相关的计数,因为信号量用于维护计数。所以 sem_wait 和 sem_post 保持计数。 强调“互斥锁用于保护部分代码不并发运行,信号量用于一个线程向另一个线程发出信号”【参考方案5】:

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

互斥:

是厕所的钥匙。一个人可以拥有钥匙——占用厕所——当时。完成后,该人将钥匙交给(释放)队列中的下一个人。

官方:“互斥锁通常用于序列化对不能由多个线程同时执行的可重入代码部分的访问。互斥锁对象只允许一个线程进入受控部分,从而迫使其他线程尝试获得对该部分的访问权限,以等待第一个线程从该部分退出。” 参考:Symbian 开发者库

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

信号量:

是免费相同的厕所钥匙的数量。例如,假设我们有四个带有相同锁和钥匙的厕所。信号量计数 - 键的计数 - 一开始设置为 4(所有四个厕所都是免费的),然后随着人们进来,计数值递减。如果所有厕所都满了,即。没有剩余的空闲键,信号量计数为 0。现在,当 eq.一个人离开厕所,信号量增加到 1(一个空闲键),并提供给队列中的下一个人。

官方:“信号量将共享资源的同时用户数限制为最大数量。线程可以请求对资源的访问(减少信号量),并且可以发出信号表明它们已经完成了对资源的使用(增加信号)。” 参考:Symbian 开发者库

【讨论】:

【参考方案6】:

尽量不让自己听起来很滑稽,但还是忍不住。

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

(我会添加这个问题,但我百分百肯定一些过分热心的版主会在不理解差异和关系之间的差异的情况下将其关闭为重复。)

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

observation.1 信号量包含互斥体

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

有一些信号量会像互斥体一样工作,称为二进制信号量,但它们并不是互斥体。

有一种特殊的成分叫做 Signaling(posix 使用 condition_variable 作为该名称),需要使用互斥体来制作 Semaphore。 将其视为通知源。如果两个或多个线程订阅了同一个通知源,则可以将消息发送给 ONE 或 ALL,以唤醒。

可能有一个或多个与信号量相关的计数器,它们由互斥锁保护。信号量最简单的场景,有一个计数器,可以是 0 或 1。

这就是混乱像季风雨一样倾泻而下的地方。

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

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

现在,发挥你的想象力,计数器的使用和何时发出信号的每一种组合都可以构成一种信号量。

    值为 0 或 1 的单个计数器,并在值变为 1 时发出信号,然后解锁等待信号的人之一 == 二进制信号量

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

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

现在回答你的问题,什么时候使用什么。 (或者更正确的问题 version.3 何时使用互斥锁以及何时使用二进制信号量,因为与非二进制信号量没有比较。) 使用互斥锁 1. 你想要一个自定义的行为,二进制信号量不提供,例如自旋锁或快速锁或递归锁。 您通常可以使用属性自定义互斥量,但自定义信号量只不过是编写新的信号量。 2. 你想要轻量级或更快的原语

使用信号量,当你想要的东西完全由它提供时。

如果您不了解二进制信号量的实现提供了什么,那么恕我直言,请使用互斥锁。

最后看一本书,而不是仅仅依靠 SO。

【讨论】:

【参考方案7】:

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

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

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

【讨论】:

【参考方案8】:

互斥锁是为了保护共享资源。 信号量是调度线程。

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

信号量: 假设我们需要进行如下计算:

c = a + b;

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

显然,除非geta()getb() 已经完成,否则我们不能执行c = a + b。 如果这三个函数是三个线程,我们需要调度这三个线程。

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);

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

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

【讨论】:

卖票就是一个很好的例子。信号量示例有点不清楚(无论如何对我来说)。 @prayagupd Semaphore 示例是以某种顺序创建线程,而售票不需要任何顺序。如果有三个人:a、b 和 c。他们来买票时,我们根本不在乎买票的顺序。但是,如果我们进行这样的计算:x = getx(); y = gety(); z = x + y; 出于某种原因,我们使用三个线程来做这三件事,现在线程的顺序非常重要,因为我们不能做x + y,除非getx 和@987654340 @ 完成了。总之,信号量用在我们关心多线程执行顺序的时候。 得到了你。这听起来类似于barrier。我可以说等到线程xy完成,然后计算z = x + y。我知道java有CyclicBarrier。另外,我不确定我是否可以说mapreduce 也是信号量用例,因为在完成所有maps 之前我不能reduce @prayagupd 是的。你可以这么说。【参考方案9】:

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

我见过的信号量大于 1 的主要用途是生产者/消费者情况,在这种情况下,你有一个固定大小的队列。

那么你有两个信号量。第一个信号量最初设置为队列中的项目数,第二个信号量设置为 0。生产者对第一个信号量执行 P 操作,添加到队列中。并在第二个上进行 V 操作。消费者对第二个信号量执行 P 操作,从队列中移除,然后对第一个信号量执行 V 操作。

这样,生产者在填满队列时被阻塞,而消费者在队列为空时被阻塞。

【讨论】:

【参考方案10】:

以上所有答案质量都很好,但这个只是为了记住。Mutex这个名字来源于互斥所以你有动机将互斥锁视为两个之间的互斥锁,一次只有一个,如果我拥有它,你只能在我释放它之后才能拥有它。另一方面,这种情况对于 不存在信号量就像一个交通信号灯(信号量这个词也是这个意思)。

【讨论】:

【参考方案11】:

互斥锁是信号量的一种特殊情况。一个信号量允许多个线程进入临界区。创建信号量时,您可以定义临界区中允许的线程数。当然,您的代码必须能够处理对该关键部分的多次访问。

【讨论】:

【参考方案12】:

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

Mutex -> 只能用于关键代码段互斥的唯一目的。

信号量 -> 可用于解决各种问题。二进制信号量可用于信令,也可解决互斥问题。初始化为0时,解决信令问题,初始化为1时,解决互斥问题。

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

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

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

【讨论】:

以上是关于我们啥时候应该使用互斥锁,啥时候应该使用信号量的主要内容,如果未能解决你的问题,请参考以下文章

锁、互斥量和信号量有啥区别?

二值信号量和互斥锁到底有什么区别?

我们应该使用带有信号量的互斥量来进行正确的同步并防止竞争条件吗?

在使用 boost 共享互斥锁时,我应该在啥情况下使用 owns_lock() 函数

linux下信号量和互斥锁的区别

有n个并发进程,设s是用于互斥的信号量,其初值s=3,当s=-2时意味啥