39

在我阅读的有关多线程的所有资源中,与信号量相比,互斥锁更常被使用和讨论。我的问题是你什么时候在互斥锁上使用信号量?我在 Boost 线程中看不到信号量。这是否意味着这些天不再使用信号量了?

据我了解,信号量允许多个线程共享资源。这只有在那些线程只读取资源而不是写入时才有可能。它是否正确?

4

9 回答 9

28

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

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

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

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

于 2010-02-28T09:25:21.610 回答
11

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

对于信号量,可以将信号量视为消耗资源,但实际上并没有获得它的所有权。这通常被称为信号量是“空的”而不是由线程拥有。信号量的特点是不同的线程可以将信号量“填充”回“满”状态。

因此,互斥锁通常用于资源的并发保护(即:MUTual EXlusion),而信号量用于线程之间的信号传输(如船舶之间的信号量标志信号)。互斥体本身不能真正用于信号发送,但信号量可以。因此,选择其中一个取决于您要做什么。

有关递归和非递归互斥体之间区别的相关主题的更多讨论,请参见我的另一个答案。

于 2010-02-28T17:12:40.490 回答
9

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

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

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

于 2010-02-28T08:58:33.593 回答
4

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

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

读取与写入资源是一个红鲱鱼 IMO,它与互斥锁和信号量之间的区别无关。如果您使用计数信号量,您可能会遇到多个线程同时访问同一资源的情况,在这种情况下,它可能必须是只读访问。在这种情况下,您可能可以使用shared_mutexfrom Boost.Thread 代替。但是信号量并不是像互斥锁那样“用于”保护资源,它们“用于”将信号从一个线程发送到另一个线程。可以使用它们来控制对资源的访问。

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

[*] 以下是使用互斥体和条件变量实现计数信号量的大致方法。当然,要实现共享信号量,您需要一个共享互斥体/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);

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

于 2010-02-28T15:32:43.770 回答
3

我觉得没有简单的方法可以在不忽略有关信号量的一些重要信息的情况下真正回答您的问题。人们写了很多关于信号量的书,所以任何一两段的答案都是一种伤害。一本很受欢迎的书是The Little Book of Semaphores ... 适合那些不喜欢大书的人 :)。

这是一篇相当长的文章,其中详细介绍了信号量的使用方式以及它们的用途。

更新:
丹在我的例子中指出了一些错误,我将把它留给提供比我更好的解释的参考资料:)。

以下参考资料显示了使用信号量的正确方法:
1. IBM 文章
2. 芝加哥大学课堂讲座
3. 我最初发布的 Netrino 文章。
4.“卖票”纸+代码。

于 2010-02-28T09:40:53.717 回答
1

取自这篇文章

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

最后是信号量类。假设您有一个确实是 CPU 密集型的方法,并且还利用了您需要控制访问的资源(使用互斥锁 :))。您还确定,最多五次对该方法的调用是您的机器可以处理的所有内容,而不会使其无响应。您最好的解决方案是使用 Semaphore 类,它允许您限制一定数量的线程对资源的访问。

于 2010-02-28T10:40:54.533 回答
0

据我了解,如今信号量是一个与 IPC 密切相关的术语。它仍然意味着许多进程可以修改受保护的变量,但是在进程之间并且此功能受操作系统支持。

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

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

于 2010-02-28T09:28:49.653 回答
0

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

于 2015-01-20T17:22:35.710 回答
-1

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

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

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

于 2010-02-28T10:51:44.720 回答