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

时间:2010-02-28 08:47:44

标签: c++ multithreading semaphore

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

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

9 个答案:

答案 0 :(得分:22)

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

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

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

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

答案 1 :(得分:11)

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

对于信号量,考虑将信号量视为消耗资源,但实际上并没有占用它。这通常被称为信号量为“空”而不是由线程拥有。信号量的特征是,不同的线程可以将信号量“填充”回“完全”状态。

因此,互斥锁通常用于资源的并发保护(即:MUTual EXlusion),而信号量用于线程之间的信令(如信号量标记信号在船之间发送)。互斥量本身不能用于信令,但信号量可以。因此,选择一个而不是另一个取决于你想要做什么。

有关涉及递归和非递归互斥锁之间区别的相关主题的更多讨论,请参阅另一个one of my answers here

答案 2 :(得分:9)

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

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

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

答案 3 :(得分:4)

Boost.Thread有互斥和条件变量。纯粹在功能方面,信号量因此是多余的[*],虽然我不知道这是否是为什么它们被省略。

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

读取与写入资源是一个红色的鲱鱼IMO,它与互斥锁和信号量之间的区别无关。如果使用计数信号量,则可能出现多个线程同时访问同一资源的情况,在这种情况下,它可能必须是只读访问。在这种情况下,您可以使用Boost.Thread中的shared_mutex。但信号量不是“用于”以互斥方式保护资源,而是“用于”从一个线程向另一个线程发送信号。可以使用来控制对资源的访问。

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

[*]以下是使用互斥锁和条件变量实现计数信号量的大致方法。要实现共享信号量,当然需要共享的互斥锁/ condvar:

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

因此,你可以用信号量做任何事情,你可以使用互斥量和条件变量。但不一定是通过实际实现信号量。

答案 4 :(得分:3)

我觉得没有简单的方法来真的回答你的问题而不忽视一些关于信号量的重要信息。人们写了many books about semaphores,所以任何一个或两个段落的答案都是不利的。一本受欢迎的书是The Little Book of Semaphores ...对于那些不喜欢大书的人来说:)。

这是一个不错的lengthy article,其中详细介绍了如何使用信号量以及如何使用这些信号量。

更新:
Dan在我的例子中指出了一些错误,我会留下一些提供比我更好的解释的参考文献:)。

以下是显示应该使用信号量的正确方法的参考文献:
1. IBM Article
2. University of Chicago Class Lecture
3. The Netrino article I originally posted.
4. The "sell tickets" paper + code.

答案 5 :(得分:1)

取自this article

  

互斥锁允许进行进程间同步。如果使用名称实例化互斥锁(如上面的代码所示),则互斥锁将变为系统范围。如果您在许多不同的应用程序之间共享相同的库并且需要阻止访问正在访问无法共享的资源的关键代码段,那么这非常有用。

     

最后,Semaphore类。假设您有一个真正占用CPU的方法,并且还使用了控制访问所需的资源(使用Mutexes :))。您还确定,对该方法的最多五次调用是关于您的所有机器可以在不使其无响应的情况下进行的。这里最好的解决方案是使用Semaphore类,它允许您限制一定数量的线程对资源的访问。

答案 6 :(得分:0)

据我所知,目前信号量是一个与IPC密切相关的术语。它仍然意味着许多进程可以修改的受保护变量,但是在进程和OS中支持此功能。

通常,我们不需要变量和简单的互斥量来满足我们的所有要求。如果我们仍然需要变量,我们可能会自己编写代码 - “变量+互斥量”以获得更多控制权。

简历:我们不在多线程中使用信号量,因为通常使用互斥量来简化和控制,我们使用信号量用于IPC,因为它支持操作系统并且是进程同步机制的正式名称。

答案 7 :(得分:0)

信号量最初是为了跨进程同步而构思的。 Windows使用类似信号量的WaitForMultipleObjects。在linux世界中,最初的pthread实现不允许跨进程共享互斥锁。现在他们做了。破解原子增量(Windows中的互锁增量)以及轻量互斥的概念是线程成为cpu调度单位后最实用的实现。如果增量和锁在一起(信号量),获取/释放锁的时间将太长,我们不能像今天那样拆分这两个单元函数以获得性能和更好的同步结构。

答案 8 :(得分:-1)

根据我在大学学习的信号量和互斥量,信号量是更多的理论对象,而互斥量是信号量的一种实现。考虑到这一点,信号量更灵活。

Mutex是高度依赖于实现的。它们已针对二进制锁定目的进行了优化。互斥锁的正常用例是二进制信号量。

通常,在尝试编写无错误的多线程代码时,简单性会有所帮助。 Mutex的使用更多,因为它们的简单性有助于避免因使用信号量而产生的复杂死锁情况。