我们何时应该使用互斥锁(mutex),何时应该使用信号量(semaphore)?
我们何时应该使用互斥锁(mutex),何时应该使用信号量(semaphore)?
这是我记住何时使用什么的方法 -
信号量(Semaphore): 当您(线程)想要睡眠直到某个其他线程告诉您醒来时,请使用信号量。信号量“down”在一个线程(生产者)中发生,信号量“up”(对同一信号量)在另一个线程(消费者)中发生。 例如:在生产者-消费者问题中,生产者希望睡眠直到至少有一个缓冲槽为空 - 只有消费者线程可以告知何时缓冲槽为空。
互斥锁(Mutex): 当您(线程)想要执行不应由任何其他线程同时执行的代码时,请使用互斥锁。互斥锁“down”在一个线程中发生,而互斥锁“up”必须稍后在同一线程中发生。 例如:如果您正在从全局链表中删除节点,则不希望另一个线程在删除节点时操纵指针。当您获取互斥锁并忙于删除节点时,如果另一个线程尝试获取相同的互斥锁,则该线程将被置于睡眠状态,直到您释放互斥锁。
自旋锁(Spinlock): 当您确实希望使用互斥锁但是您的线程不允许睡眠时,请使用自旋锁。 例如:在OS内核中的中断处理程序绝不能睡眠。如果它这样做,系统将冻结/崩溃。如果您需要从中断处理程序插入节点到全局共享链表中,请获取自旋锁-插入节点-释放自旋锁。
互斥量是一个互斥对象,类似于信号量,但它只允许一次加锁,并且其所有权限制可能比信号量更严格。
可以将其视为等效于普通计数信号量(计数为1),并要求只能由锁定它的同一线程释放(a)。
另一方面,信号量具有任意数量的计数,并且可以同时被那么多个锁定者锁定。并且它可能没有要求由声称它的同一线程释放(但如果没有,则必须仔细跟踪当前谁有责任,就像分配的内存一样)。
因此,如果您有多个资源实例(例如三个磁带驱动器),则可以使用计数为3的信号量。请注意,这并不告诉您哪些磁带驱动器属于您,只是告诉您有一定数量。
此外,使用信号量,单个锁定者可以锁定多个资源实例,例如进行磁带到磁带的复制。如果您有一个资源(例如不想破坏的内存位置),则互斥量更合适。
等效操作包括:
Counting semaphore Mutual exclusion semaphore
-------------------------- --------------------------
Claim/decrease (P) Lock
Release/increase (V) Unlock
顺带一提:如果你曾经对用于声明和释放信号量的奇怪字母(P
和V
)感到困惑,那是因为发明者是荷兰人。 在荷兰语中:
(a)......或者可以将其视为与信号量完全不同的东西,这可能会更安全,考虑到它们几乎总是用途不同。
虽然 @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
函数中没有线程时,信号量却不会做这个动作 :-) 在我看来,它们两者都与资源有关,而通知其他线程则是保护的副作用(从性能角度来看非常重要)。 - paxdiablo参见“厕所例子” - http://pheatt.emporia.edu/courses/2010/cs557f10/hand07/Mutex%20vs_%20Semaphore.htm:
Mutex(互斥量):
是一把厕所的钥匙。一次只能有一个人拥有这把钥匙,使用厕所。当使用完之后,持有者将钥匙交给排队中的下一个人。
正式定义:“互斥锁通常用于串行访问重入代码的区域,该代码不能被多个线程同时执行。互斥锁对象仅允许一个线程进入受控区域,迫使试图访问该区域的其他线程等待,直到第一个线程从该区域退出。” 参考文献:Symbian Developer Library
(实际上,互斥锁就是值为1的信号量。)
Semaphore(信号量):
是相同厕所钥匙的数量。例如,假设我们有四个带有相同锁和钥匙的厕所。在开始时,信号量计数 - 钥匙计数 - 设置为4(所有四个厕所都是空闲的),然后随着人们进入,计数值递减。如果所有厕所都满了,即没有空闲的钥匙了,则信号量计数为0。现在,当一个人离开厕所时,信号量增加1(一个自由的钥匙),并交给排队中的下一个人。
正式定义:“信号量限制共享资源的最大同时使用者数量。线程可以请求访问该资源(递减信号量),并且可以发出已经完成对该资源的使用的信号(递增信号量)。” 参考文献:Symbian Developer Library
Mutex是用来保护共享资源的。
Semaphore是用来调度线程的。
Mutex:
假设有一些门票要出售。我们可以模拟这样一个场景:许多人同时购买门票,每个人都是一个购票的线程。显然,我们需要使用Mutex来保护门票,因为它是共享资源。
Semaphore:
假设我们需要进行以下计算:
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);
x = getx(); y = gety(); z = x + y;
出于某种原因,我们使用三个线程来完成这三件事,现在线程的顺序非常重要,因为除非getx
和gety
已经完成,否则我们无法执行x + y
。总之,当我们关心多线程执行的顺序时,会使用信号量。 - Yvesx
和y
完成,然后计算z = x + y
。我知道Java有CyclicBarrier
。此外,我不确定是否可以说mapreduce
也是信号量用例,因为在所有map
完成之前我无法进行reduce
。 - prayagupa我认为问题应该是mutex和二进制信号量之间的区别。
Mutex = 它是一种所有权锁机制,只有获取锁的线程才能释放锁。
二进制信号量 = 它更像是一个信号机制,任何其他更高优先级的线程如果想要可以发信号并获取锁。
正如所指出的,计数为1的信号量与“二进制”信号量以及互斥锁是相同的。
我看到使用计数大于1的信号量的主要情况是在生产者/消费者的情况下,你有一个固定大小的队列。
然后你需要两个信号量。第一个信号量最初设置为队列中的项目数量,第二个信号量设置为0。生产者对第一个信号量执行P操作,将其添加到队列中,并对第二个信号量执行V操作。消费者对第二个信号量执行P操作,从队列中删除,然后对第一个信号量执行V操作。
通过这种方式,当生产者填满队列时,它会被阻塞,而当队列为空时,消费者会被阻塞。