在什么情况下你在C++中使用信号量而不是互斥量?

jas*_*ine 36 c++ multithreading semaphore

在我读过的关于多线程的资源中,与信号量相比,更经常使用和讨论互斥.我的问题是你什么时候使用信号量而不是互斥量?我没有在Boost线程中看到信号量.这是否意味着信号量这些天不再使用太多?

据我所知,信号量允许多个线程共享资源.只有当这些线程只读取资源但不写入时,才可能这样做.它是否正确?

Nik*_*iki 23

互斥锁的典型用例(在任何时候只允许一个线程访问资源)比信号量的典型用法更常见.但信号量实际上是更一般的概念:互斥体(几乎)是信号量的特例.

典型的应用程序是:您不希望创建超过(例如)5个数据库连接.无论有多少工作线程,他们都必须共享这5个连接.或者,如果您在N核计算机上运行,​​您可能希望确保某些CPU /内存密集型任务不会同时在多于N个线程中运行(因为这只会因上下文切换而降低吞吐量)和缓存捶打效果).您甚至可能希望将并行CPU /内存密集型任务的数量限制为N-1,因此系统的其余部分不会饿死.或者想象某个任务需要大量内存,因此同时运行该任务的N个实例将导致分页.您可以在此处使用信号量,以确保此特定任务的N个实例不会同时运行.

编辑/ PS:从您的问题"只有当这些线程只读取资源但不写入时才有可能.这是正确的吗?" 和你的评论一样,在我看来,你好像在考虑将资源作为变量或流,可以读取或写入,并且一次只能由一个线程写入.别.这在这方面具有误导性.

想想像"水"这样的资源.你可以用水来洗碗.我可以用水同时洗碗.我们不需要任何同步,因为我们俩都有足够的水.我们不一定使用相同的水.(而且你不能"读"或"写"水.)但水量是有限的.因此,任何一方都不可能同时洗碗.这种同步是用信号量完成的.通常只有水,但有其他有限的资源,如内存,磁盘空间,IO吞吐量或CPU核心.


Tal*_*eff 11

互斥体和信号量之间差异的本质与所有权概念有关.当使用互斥锁时,我们认为该线程拥有互斥锁,并且相同的线程必须稍后释放互斥锁以释放资源.

对于信号量,考虑将信号量视为消耗资源,但实际上并未对其拥有所有权.这通常被称为信号量为"空"而不是由线程拥有.信号量的特征是,不同的线程可以将信号量"填充"回"完全"状态.

因此,互斥体通常用于资源的并发保护(即:MUTual EXlusion),而信号量用于线程之间的信令(如船舶之间的信号量标志信令).互斥量本身不能用于信令,但信号量可以.因此,选择一个而不是另一个取决于您要做的事情.

请参阅我在这里的另一个答案,以获得有关递归和非递归互斥锁之间区别的相关主题的更多讨论.


R S*_*hko 9

控制对多个线程共享的有限数量资源的访问(进程间或进程内).

在我们的应用程序中,我们有一个非常繁重的资源,我们不想为每个M工作线程分配一个.由于工作线程只需要将资源用于其工作的一小部分,因此我们很少同时使用多个资源.

因此,我们分配了N个这些资源,并将它们放在一个初始化为N的信号量后面.当N个线程试图使用该资源时,它们只会阻塞直到一个可用.


Ste*_*sop 4

Boost.Thread 有互斥体和条件变量。纯粹就功能而言,信号量因此是多余的[*],尽管我不知道这是否就是它们被省略的原因。

信号量是一种更基本的原语,更简单,并且可能实现得更快,但不具有优先级反转避免功能。它们可以说比条件变量更难使用,因为它们要求客户端代码以某种适当的方式确保帖子数量与等待数量“匹配”。使用条件变量,很容易容忍虚假帖子,因为没有人在不检查条件的情况下实际上做任何事情。

在我看来,读资源与写资源是一个转移注意力的话题,它与互斥锁和信号量之间的区别无关。如果您使用计数信号量,则可能会遇到多个线程同时访问同一资源的情况,在这种情况下,它可能必须是只读访问。在这种情况下,您也许可以使用shared_mutexBoost.Thread。但信号量并不像互斥体那样“用于”保护资源,它们“用于”从一个线程向另一个线程发送信号。可以使用它们来控制对资源的访问。

这并不意味着信号量的所有使用都必须与只读资源相关。例如,您可以使用二进制信号量来保护读/写资源。不过,这可能不是一个好主意,因为互斥体通常可以为您提供更好的调度行为。

[*] 下面大致介绍了如何使用互斥锁和条件变量来实现计数信号量。当然,要实现共享信号量,您需要共享互斥体/条件变量:

struct sem {
    mutex m;
    condvar cv;
    unsigned int count;
};

sem_init(s, value)
    mutex_init(s.m);
    condvar_init(s.cv);
    count = value;

sem_wait(s)
    mutex_lock(s.m);
    while (s.count <= 0) {
        condvar_wait(s.cv, s.m);
    }
    --s.count;
    mutex_unlock(s.m);

sem_post(s)
    mutex_lock(s.m);
    ++s.count;
    condvar_broadcast(s.cv)
    mutex_unlock(s.m);
Run Code Online (Sandbox Code Playgroud)

因此,您可以使用信号量执行的任何操作,都可以使用互斥体和条件变量执行。但不一定是通过实际实现信号量来实现。