信号量vs互斥锁(Semaphore vs Mutex)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了信号量vs互斥锁(Semaphore vs Mutex)相关的知识,希望对你有一定的参考价值。

参考技术A 信号量是一个被线程共享的非负变量。信号量是一个发信号的机制。一个等待一个信号量的线程可以被其他线程通知(signal)。这个机制通过 wait 和 signal 两个原子操作(atomic operations)来实现进程同步。

一个信号量要么允许访问资源,要么不允许访问资源。二者只能选其一。而具体是哪一种,则要看设置。

详情可参考《 信号量:二进位信号量和计数信号量 》这篇文章。

互斥锁其实是一个对象。Mutex的全称是Mutual Exclusion Object,也就是互斥锁是一个互斥对象。它是一种特殊的二进位信号量(binary semaphore),用来控制访问共享区域资源。它包括一个优先级继承机制,以避免扩展的优先级反转问题。它允许当前优先级较高的任务在阻塞状态下维持的时间尽可能的少。然而,优先级继承并不能完全避免优先级反转,而只会最小化其影响。

对于单个缓冲区(single buffer),我们可以将4kb缓冲区分成四个1kb缓冲区。信号量可以与这四个缓冲区相关联。这允许用户和生产者同时处理不同的缓冲区。

互斥锁用于提供互斥,它使得拥有钥匙(key or mutex)的生产者才能访问资源。只要生产者占用了缓冲区(buffer),用户必须等待,反之亦然。在互斥锁的机制中,整块缓冲区始终只能提供给一个线程访问。

下面列举信号量的优点:

下面列举互斥锁的优点:

下面列举信号量的缺点:

下面列举互斥锁的缺点:

信号量与互斥锁的区别

之前遇到一个问题,信号量和互斥锁的区别是什么。一时忘了思考,今天才想到这个问题,翻阅知乎和stackoverflow,理解了之后做简单整理

一、定义

mutex,互斥锁,用于序列化对一部分可重入代码的访问,这些代码不能由多个线程同时执行

semaphore,信号量,将共享资源的并发用户数限制为最大数量

二、使用

Mutex:假设我们有关键部分线程T1想要访问它然后它遵循以下步骤:

  1. 使用关键部分
  2. 开锁

二进制信号量:它基于信令等待和信号工作。等待(s)将“s”值减少一个通常“s”值用值“1”初始化,信号(s)将“s”值增加1。如果“s”值为1表示没有人使用临界区,则值为0表示临界区正在使用中。假设线程T2正在使用临界区,那么它遵循以下步骤:

  1. wait(s)//最初s值在调用之后等于它的值减1,即0
  2. 使用关键部分
  3. signal(s)//现在s值增加,变为1

三、具体分析(引自知乎)

mutex,典型的例子就是买票: 票是共享资源,现在有两个线程同时过来买票。如果你不用mutex在线程里把票锁住,那么就可能出现“把同一张票卖给两个不同的人(线程)”的情况。 我想这个不需要多解释了。

一般人不明白semaphore和mutex的区别,根本原因是不知道semaphore的用途。 semaphore的用途,一句话:调度线程。 有的人用semaphore也可以把上面例子中的票“保护"起来以防止共享资源冲突,必须承认这是可行的,但是semaphore不是让你用来做这个的;如果你要做这件事,请用mutex。

在网上、包括stackoverflow等著名论坛上,有一个流传很广的厕所例子: mutex是一个厕所一把钥匙,谁抢上钥匙谁用厕所,谁没抢上谁就等着;semaphore是多个同样厕所多把同样的钥匙 ---- 只要你能拿到一把钥匙,你就可以随便找一个空着的厕所进去。 事实上,这个例子对初学者、特别是刚刚学过mutex的初学者来说非常糟糕 ----- 我第一次读到这个例子的第一反应是:semaphore是线程池???所以,请务必忘记这个例子。 另外,有人也会说:mutex就是semaphore的value等于1的情况。 这句话不能说不对,但是对于初学者来说,请先把这句话视为错误;等你将来彻底融会贯通这部分知识了,你才能真正理解上面这句话到底是什么意思。总之请务必记住:mutex干的活儿和semaphore干的活儿不要混起来。

在这里,我模拟一个最典型的使用semaphore的场景: a源自一个线程,b源自另一个线程,计算c = a + b也是一个线程。(即一共三个线程)

显然,第三个线程必须等第一、二个线程执行完毕它才能执行。 在这个时候,我们就需要调度线程了:让第一、二个线程执行完毕后,再执行第三个线程。 此时,就需要用semaphore了。
 
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);

// semaphore的机制我在这里就不讲了,百度一下你就知道。
// semaphore_increase对应sem_post
// semaphore_decrease对应sem_wait
这就是semaphore最典型的用法。 说白了,调度线程,就是:一些线程生产(increase)同时另一些线程消费(decrease),semaphore可以让生产和消费保持合乎逻辑的执行顺序。 而线程池是程序员根据具体的硬件水平和不同的设计需求、为了达到最佳的运行效果而避免反复新建和释放线程同时对同一时刻启动的线程数量的限制,这完全是两码事。 比如如果你要计算z = a + b +...+ x + y ...的结果,同时每个加数都是一个线程,那么计算z的线程和每个加数的线程之间的逻辑顺序是通过semaphore来调度的;而至于你运行该程序的时候到底要允许最多同时启动几个线程,则是用线程池来实现的。

简而言之,锁是服务于共享资源的;而semaphore是服务于多个线程间的执行的逻辑顺序的

请回头看那个让大家忘记的厕所例子。我之所以让大家忘记这个例子,是因为如果你从这个角度去学习semaphore的话,一定会和mutex混为一谈。semaphore的本质就是调度线程 ---- 在充分理解了这个概念后,我们再看这个例子。

semaphore是通过一个值来实现线程的调度的,因此借助这种机制,我们也可以实现对线程数量的限制。而当我们把线程数量限制为1时,你会发现:共享资源受到了保护 ------ 任意时刻只有一个线程在运行,因此共享资源当然等效于受到了保护。但是我要再提醒一下,如果你要对共享资源进行保护,请用mutex;到底应该用条件锁还是用semaphore,请务必想清楚。通过semaphore来实现对共享资源的保护的确可行但是是对semaphore的一种错用

只要你能搞清楚锁、条件锁和semaphore为什么而生、或者说它们是面对什么样的设计需求、为了解决什么样类型的问题才出现的,你自然就不会把他们混淆起来。

作者:二律背反 链接:https://www.zhihu.com/question/47704079/answer/135859188 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

以上是关于信号量vs互斥锁(Semaphore vs Mutex)的主要内容,如果未能解决你的问题,请参考以下文章

死锁现象与解决方案,开启线程的2种方式,守护线程,线程VS进程,线程互斥锁,信号量

Semaphore(信号量)

python-信号量(semaphore)

12第七周-网络编程 - 线程中的信号量(Semaphore)

信号量与互斥锁

并发编程中:Semaphore信号量与lock的区别