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

发布于 2024-08-23 18:16:10 字数 163 浏览 10 评论 0原文

在我阅读的有关多线程的资源中,与信号量相比,互斥体的使用和讨论更为频繁。我的问题是什么时候使用信号量而不是互斥体?我在 Boost 线程中没有看到信号量。这是否意味着信号量如今不再被广泛使用?

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

Throughout the resources I've read about multithreading, mutex is more often used and discussed compared to a semaphore. My question is when do you use a semaphore over a mutex? I don't see semaphores in Boost thread. Does that mean semaphores no longer used much these days?

As far as I've understand, semaphores allow a resource to be shared by several threads. This is only possible if those threads are only reading the resource but not writing. Is this correct?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

烙印 2024-08-30 18:16:10

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

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

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

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

The typical use case for a mutex (allowing only one thread access to a resource at any time) is far more common than the typical uses if a semaphore. But a semaphore is actually the more general concept: A mutex is (almost) a special case of a semaphore.

Typical applications would be: You don't want to create more than (e.g.) 5 database connections. No matter how many worker threads there are, they have to share these 5 connections. Or, if you run on a N-core machine, you might want to make sure that certain CPU/memory-intensive tasks don't run in more than N threads at the same time (because that would only reduce throughput due to context switches and cache thrashing effects). You might even want to limit the number of parallel CPU/memory intensive tasks to N-1, so the rest of the system doesn't starve. Or imagine a certain task needs a lot of memory, so running more than N instances of that task at the same time would lead to paging. You could use a semaphore here, to make sure that no more than N instances of this particular task run at the same time.

EDIT/PS: From your question "This is only possible if those threads are only reading the resource but not writing. Is this correct?" and your comment, it seems to me as if you're thinking of a resource as a variable or a stream, that can be read or written and that can only be written to by one thread at a time. Don't. This is misleading in this context.

Think of resources like "water". You can use water to wash your dishes. I can use water to wash my dishes at the same time. We don't need any kind of synchronization for that, because there is enough water for both of us. We don't necessarily use the same water. (And you can't "read" or "write" water.) But the total amount of water is finite. So it's not possible for any number of parties to wash their dishes at the same time. This kind of synchronization is done with a semaphore. Only usually not with water but with other finite resources like memory, disk space, IO throughput or CPU cores.

无悔心 2024-08-30 18:16:10

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

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

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

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

The essence of the difference between a mutex and a semaphore has to do with the concept of ownership. When a mutex is taken, we think of that thread as owning the mutex and that same thread must later release the mutex back to release the resource.

For a semaphore, think of taking the semaphore as consuming the resource, but not actually taking ownership of it. This is generally referred to as the semaphore being "empty" rather than owned by a thread. The feature of the semaphore is then that a different thread can "fill" the semaphore back to "full" state.

Therefore, mutexes are usually used for the concurrency protection of resources (ie: MUTual EXlusion) while semaphores are used for signaling between threads (like semaphore flags signaling between ships). A mutex by itself can't really be used for signaling, but semaphores can. So, selecting one over the other depends on what you are trying to do.

See another one of my answers here for more discussion on a related topic covering the distinction between recursive and non-recursive mutexes.

如何视而不见 2024-08-30 18:16:10

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

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

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

To control access to a limited number of resources being shared by multiple threads (either inter- or intra-process).

In our application, we had a very heavy resource and that we did not want to allocate one for each of the M worker threads. Since a worker thread needed the resource for just one small part of their job, we rarely were using more then a couple of the resources simultaneously.

So, we allocated N of those resources and put them behind a semaphore initialized to N. When more then N threads were trying to use the resource, they would just block until one was available.

乄_柒ぐ汐 2024-08-30 18:16:10

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

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

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

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

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

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

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

Boost.Thread has mutexes and condition variables. Purely in terms of functionality, semaphores are therefore redundant[*], although I don't know if that's why they're omitted.

Semaphores are a more basic primitive, simpler, and possibly implemented to be faster, but don't have priority-inversion avoidance. They're arguably harder to use than condition variables, because they require the client code to ensure that the number of posts "matches" the number of waits in some appropriate way. With condition variables it's easy to tolerate spurious posts, because nobody actually does anything without checking the condition.

Read vs. write resources is a red herring IMO, it has nothing to do with the difference between a mutex and a semaphore. If you use a counting semaphore, you could have a situation where multiple threads are concurrently accessing the same resource, in which case it would presumably have to be read-only access. In that situation, you might be able to use shared_mutex from Boost.Thread instead. But semaphores aren't "for" protecting resources in the way mutexes are, they're "for" sending a signal from one thread to another. It's possible to use them to control access to a resource.

That doesn't mean that all uses of semaphores must relate to read-only resources. For example, you can use a binary semaphore to protect a read/write resource. Might not be a good idea, though, since a mutex often gives you better scheduling behaviour.

[*] Here's roughly how you implement a counting semaphore using a mutex and a condition variable. To implement a shared semaphore of course you need a 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);

Therefore, anything you can do with semaphores, you can do with mutexes and condition variables. Not necessarily by actually implementing a semaphore, though.

心不设防 2024-08-30 18:16:10

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

这是一篇不错的长篇文章,其中详细介绍了如何使用信号量以及它们的用途。

更新:
丹指出了我的示例中的一些错误,我将留下参考文献,这些参考文献提供了比我的更好的解释:)。

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

I feel like there is no simple way to REALLY answer your question without disregarding some important information about semaphores. People have written many books about semaphores, so any one or two paragraph answer is a disservice. A popular book is The Little Book of Semaphores... for those who don't like big books :).

Here is a decent lengthy article which goes into a LOT of the details on how semaphores are used and how they're intended to be used.

Update:
Dan pointed out some mistakes in my examples, I'll leave it with the references which offer MUCH better explanations than mine :).

Here are the references showing the RIGHT ways one should use a semaphore:
1. IBM Article
2. University of Chicago Class Lecture
3. The Netrino article I originally posted.
4. The "sell tickets" paper + code.

思念绕指尖 2024-08-30 18:16:10

摘自这篇文章< /a>:

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

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

As taken from this article:

A mutex allows inter-process synchronisation to occur. If you instantiate a mutex with a name (as in the code above), the mutex becomes system-wide. This is really useful if you're sharing the same library between many different applications and need to block access to a critical section of code that is accessing resources that can't be shared.

Finally, the Semaphore class. Let's say you have a method that is really CPU intensive, and also makes use of resources that you need to control access to (using Mutexes :)). You've also determined that a maximum of five calls to the method is about all your machine can hanlde without making it unresponsive. Your best solution here is to make use of the Semaphore class which allows you to limit a certain number of threads' access to a resource.

暮光沉寂 2024-08-30 18:16:10

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

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

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

As far as I understand semaphores is a strongly IPC-related term these days. It still means protected variable many processes can modify, but among processes and this feature is supported by OS.

Usually, we don't need a variable and a simple mutex cover all our requirements. If we still need a variable, probably, we code it ourselves - "variable + mutex" to get more control.

Resume: we don't use semaphores in multithreading because usually use mutex for simplicity and control, and we use semaphores for IPC because it's OS-supported and an official name for processes synchronization mechanism.

走走停停 2024-08-30 18:16:10

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

Semaphores was conceived originally for synchronization across processes. Windows uses WaitForMultipleObjects that is like a semaphore. In linux world, the initial pthread implementation did not allow a mutex to be shared across process. Now they do. The concept of breaking the atomic increment (interlocked increment in Windows) along with light weight mutex is most practical implementation these days after threads became the unit of scheduling for cpu. If the increment and the lock were together (semaphore), the time to acquire / release locks will be too long and we cannot split those 2 unit functions as we do today for performance and better synchronization constructs.

鲜肉鲜肉永远不皱 2024-08-30 18:16:10

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

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

一般来说,当尝试编写无错误的多线程代码时,简单性会有所帮助。互斥锁的使用较多,因为它们的简单性有助于避免因使用信号量而出现的复杂死锁情况。

From what I learned about semaphores and mutex's in college, semaphore are more theoretical objects while mutex's are one implementation of semaphores. Taking that into account, semaphores are more flexible.

Mutex's are highly implementation dependent. They have been optimized for their binary locking purpose. The normal use case of a mutex is a binary semaphore.

In general, when trying to write bug-free multithreaded code, simplicity helps. Mutex's are used more because their simplicity helps avoid complex deadlock scenarios that arise from using semaphores.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文