我们什么时候应该使用互斥锁,什么时候应该使用信号量?
以下是我记得什么时候使用什么 -
信号量:当你(线程)想睡觉时使用信号量,直到其他线程告诉你醒来。信号量“向下”发生在一个线程(生产者)中,信号量“向上”(对于相同的信号量)发生在另一个线程(消费者)中,例如:在生产者-消费者问题中,生产者想要休眠直到至少一个缓冲区为空 - 仅消费者线程可以判断缓冲区插槽何时为空。
互斥锁:当您(线程)想要执行不应由任何其他线程同时执行的代码时,请使用互斥锁。互斥锁“向下”发生在一个线程中,而互斥锁“向上”必须稍后发生在同一个线程中。例如:如果您要从全局链表中删除一个节点,您不希望另一个线程在您删除该节点时使用指针乱七八糟。当您获取一个互斥锁并忙于删除一个节点时,如果另一个线程试图获取相同的互斥锁,它将进入睡眠状态,直到您释放该互斥锁。
自旋锁:当您确实想使用互斥锁但不允许您的线程休眠时,请使用自旋锁。例如:操作系统内核中的中断处理程序绝不能休眠。如果是这样,系统将冻结/崩溃。如果需要从中断处理程序中插入一个节点到全局共享链表,获取一个自旋锁-插入节点-释放自旋锁。
互斥体是一种互斥对象,类似于信号量,但一次只允许一个储物柜,其所有权限制可能比信号量更严格。
它可以被认为等同于正常的计数信号量(计数为 1),并且要求它只能由锁定它的同一线程释放(a)。
另一方面,信号量具有任意计数,并且可以同时被那么多储物柜锁定。并且它可能不要求它由声明它的同一线程释放(但是,如果没有,您必须仔细跟踪当前负责它的人,就像分配的内存一样)。
因此,如果您有多个资源实例(例如三个磁带驱动器),您可以使用计数为 3 的信号量。请注意,这并不能告诉您您拥有哪些磁带驱动器,只是您拥有一定数量。
同样使用信号量,单个锁柜可以锁定资源的多个实例,例如磁带到磁带的复制。如果您有一个资源(例如您不想破坏的内存位置),则互斥锁更合适。
等效操作是:
Counting semaphore Mutual exclusion semaphore
-------------------------- --------------------------
Claim/decrease (P) Lock
Release/increase (V) Unlock
除此之外:如果您曾经对用于声明和释放信号量的奇怪字母(P
和 V
)感到好奇,那是因为发明者是荷兰人。用那种语言:
Probeer te verlagen:意思是尽量降低;
Verhogen:增加的意思。
(a) ...或者它可以被认为是与信号量完全不同的东西,考虑到它们几乎总是不同的用途,它可能更安全。
了解互斥锁不是计数为 1 的信号量非常重要!
这就是存在二进制信号量(实际上是计数为 1 的信号量)之类的东西的原因。
Mutex 和 Binary-Semaphore 的区别在于所有权原则:
互斥锁由任务获取,因此也必须由同一任务释放。这使得修复二进制信号量的几个问题(意外释放、递归死锁和优先级反转)成为可能。
警告:我写了“使之成为可能”,是否以及如何解决这些问题取决于操作系统的实现。
因为互斥锁必须由同一个任务释放,所以对于任务的同步不是很好。但如果与条件变量结合使用,您将获得构建各种 IPC 原语的非常强大的构建块。
所以我的建议是:如果你有干净地实现的互斥锁和条件变量(比如 POSIX pthreads)使用这些。
仅当信号量完全适合您要解决的问题时才使用信号量,不要尝试构建其他原语(例如 rw-locks 信号量,使用互斥锁和条件变量)
互斥量和信号量之间有很多误解。到目前为止,我发现的最佳解释是在这篇 3 部分文章中:
Mutex vs. Semaphores – Part 1: Semaphores
Mutex vs. Semaphores – Part 2: The Mutex
Mutex vs. Semaphores – Part 3 (final part): Mutual Exclusion Problems
虽然@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 了解更多说明
sema_wait
中没有线程时,信号量不会 :-) 在我看来,它们是 both关于资源和传递给其他线程的通知是保护的副作用(非常重要,性能方面)。
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_lock
和 pthread_mutex_unlock
请参阅“厕所示例” - http://pheatt.emporia.edu/courses/2010/cs557f10/hand07/Mutex%20vs_%20Semaphore.htm:
互斥体:
是厕所的钥匙。一个人可以拥有钥匙——占用厕所——当时。完成后,该人将钥匙交给(释放)队列中的下一个人。
官方:“互斥锁通常用于序列化对不能由多个线程同时执行的可重入代码部分的访问。互斥锁对象只允许一个线程进入受控部分,从而迫使其他线程尝试访问该部分等待直到第一个线程从该部分退出。”参考:Symbian 开发者库
(互斥锁实际上是一个值为 1 的信号量。)
信号:
是免费相同的厕所钥匙的数量。例如,假设我们有四个带有相同锁和钥匙的厕所。信号量计数 - 键的计数 - 一开始设置为 4(所有四个厕所都是免费的),然后随着人们进来,计数值递减。如果所有厕所都满了,即。没有剩余的空闲键,信号量计数为 0。现在,当 eq.一个人离开厕所,信号量增加到 1(一个空闲键),并提供给队列中的下一个人。
正式地:“信号量将共享资源的同时用户数限制为最大数量。线程可以请求访问资源(减少信号量),并且可以发出信号表明它们已经完成了对资源的使用(增加信号量)。 "参考:Symbian 开发者库
尽量不让自己听起来很滑稽,但还是忍不住。
您的问题应该是 mutex 和 semaphores 之间有什么区别?更准确的问题应该是,“互斥量和信号量之间的关系是什么?”
(我会添加这个问题,但我百分百肯定一些过分热心的版主会在不理解差异和关系之间的区别的情况下将其关闭为重复。)
在对象术语中,我们可以观察到:
观察.1 信号量包含互斥体
观察.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 == 屏障信号量时锁定/等待(如果他们不调用它,那么他们应该调用它。)
现在到你的问题,什么时候使用什么。 (或者更正确的问题版本 3 何时使用互斥量以及何时使用二进制信号量,因为无法与非二进制信号量进行比较。)在 1. 您想要自定义行为时使用互斥量,而二进制不提供该行为信号量,例如自旋锁或快速锁或递归锁。您通常可以使用属性自定义互斥量,但自定义信号量只不过是编写新的信号量。 2.你想要轻量级或更快的原语
当你想要的东西完全由它提供时,使用信号量。
如果您不了解二进制信号量的实现提供了什么,那么恕我直言,请使用互斥锁。
最后阅读一本书,而不是仅仅依靠 SO。
互斥锁是为了保护共享资源。信号量是调度线程。
Mutex:想象一下有一些票要卖。我们可以模拟一个多人同时买票的情况:每个人都是一个买票的线程。显然我们需要使用互斥锁来保护票据,因为它是共享资源。
信号量:假设我们需要进行如下计算:
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);
在信号量的帮助下,上面的代码可以确保 t3
在 t1
和 t2
完成它们的工作之前不会完成它的工作。
总之,信号量是为了让线程按照逻辑顺序执行,而互斥量是为了保护共享资源。所以即使有些人总是说互斥量是一个初始值为 1 的特殊信号量,它们也不是一回事。你也可以这样说,但请注意它们在不同的情况下使用。即使你能做到,也不要一个接一个地替换。
x = getx(); y = gety(); z = x + y;
出于某种原因,我们使用三个线程来做这三件事,现在线程的顺序非常重要,因为我们不能做 x + y
,除非 getx
和 {4 } 完成了。总之,信号量是在我们关心多线程的执行顺序时使用的。
x
和 y
完成,然后计算 z = x + y
。我知道java有CyclicBarrier
。另外,我不确定我是否可以说 mapreduce
也是信号量用例,因为在完成所有 map
之前我不能reduce
。
我认为问题应该是互斥量和二进制信号量之间的区别。
Mutex = 它是一种所有权锁机制,只有获得锁的线程才能释放锁。
binary Semaphore = 它更像是一种信号机制,如果需要,任何其他更高优先级的线程都可以发出信号并获取锁。
以上所有答案质量都很好,但这只是为了记住。互斥锁这个名字来源于互斥,因此你有动力将互斥锁视为两个之间的互斥锁,一次只有一个,如果我拥有它,只有在我释放它之后才能拥有它。另一方面,Semaphore 不存在这种情况,就像一个交通信号灯(Semaphore 这个词也意味着)。
正如所指出的,计数为 1 的信号量与“二进制”信号量相同,后者与互斥锁相同。
我见过的信号量大于 1 的主要情况是生产者/消费者情况,在这种情况下,您有一个固定大小的队列。
那么你有两个信号量。第一个信号量最初设置为队列中的项目数,第二个信号量设置为 0。生产者对第一个信号量执行 P 操作,添加到队列中。并在第二个上进行 V 操作。消费者对第二个信号量执行 P 操作,从队列中移除,然后对第一个信号量执行 V 操作。
这样,生产者在填满队列时被阻塞,而消费者在队列为空时被阻塞。
互斥锁是信号量的一种特殊情况。一个信号量允许多个线程进入临界区。创建信号量时,您可以定义临界区允许线程的数量。当然,您的代码必须能够处理对该关键部分的多次访问。
我发现@Peer Stritzinger 的答案是正确的。
我想在他的回答中添加 David R Butenhof
的书 Programming with POSIX Threads
中的以下引用。在第 3 章的第 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
,那么您将不会得到互斥。使用互斥体,根据定义,这种行为是不可能的。
二进制信号量和互斥量是不同的。从操作系统的角度来看,二进制信号量和计数信号量的实现方式相同,二进制信号量的值可以是 0 或 1。
Mutex -> 只能用于关键代码段互斥的唯一目的。
信号量 -> 可用于解决各种问题。二进制信号量可用于信令,也可解决互斥问题。初始化为0时,解决信令问题,初始化为1时,解决互斥问题。
当资源数量较多,需要同步时,我们可以使用计数信号量。
在我的博客中,我详细讨论了这些主题。
https://designpatterns-oo-cplusplus.blogspot.com/2015/07/synchronization-primitives-mutex-and.html