为什么互斥锁 (std::mutex) 很重?

Joh*_*ohy 4 c++ multithreading boost mutex c++11

在这个网站上,我经常在其他论坛上读到诸如“互斥量很重,最好使用其他东西”之类的短语。但我真的找不到解释为什么它很重?此外,如果我们在 C++20 之前谈论标准 C++11,我们基本上只有 std::mutex,与锁或 condition_variable 一起使用,以实现线程安全,我希望 std 中的某些东西非常有效,尤其是如果它是执行某些任务的唯一工具(在 C++20 之前),则在这种情况下是线程安全的。那么为什么互斥体,尤其是 std::mutex 很重呢?我们作为 C++ 开发人员应该使用什么?来自提升的东西?

rus*_*tyx 6

互斥体被认为是“重的”,因为它们通常被认为会导致系统调用,即到内核的往返。由于特权代码和非特权代码之间的上下文切换,内核之旅需要 1,000 多个 CPU 周期。

如今,在许多操作系统中,互斥锁被优化为在发生争用之前不会进入内核。例如,在 Linux 中它是使用 futex(“快速用户空间互斥锁”)实现的,在 Windows 中 - SRW 锁。但是,一旦发生争用,就会有内核的行程。一旦一个线程需要等待,它就会被操作系统“置于睡眠状态”,并且在释放锁的那一刻和该线程被安排再次执行的时间之间会有很大的延迟。

如果您需要同步,有时循环一个简单的atomic就足够了。如果争用很少而且很短,那么您可以使用“自旋锁”实现更好的性能,即循环直到满足特定条件。即使循环 10000 次,它也可以比单个系统调用更快。

然而,在实践中,互斥体将在性能和便利性之间提供足够的平衡。因此,除非您计算纳秒(如在 HFT 或实时应用程序中),否则我不会担心它。


Com*_*sMS 5

std::mutex被设计为围绕操作系统的本机互斥工具的轻量级便携式包装器。如果您的目标是调用这些工具,mutex与直接调用 OS 原生 API 相比,只会引入非常微不足道的开销。

但是,根据您的用例是什么,使用操作系统工具可能不是最佳解决方案。例如,为了保护数据免受并发访问,您还可以从较低级别的原语(如std::atomic. 然而,这将是一种不同的锁定算法。特别是,std::mutex如果不能立即获得互斥锁,将使等待线程进入睡眠状态,这是您在不与操作系统交谈的情况下无法做到的。但在某些情况下,这种更简单的锁定算法足以完成工作。这里的一个流行示例是锁争用预计仅在极少数情况下发生的情况。

话虽如此,这样的想法会让您深入了解专家级并发编程。除非您有特定的顾虑需要担心微优化,例如滚动您自己的锁定,否则std::mutex是要走的路,并且它的开销在合理的范围内进行。


Yak*_*ont 5

各种同步都是“重”的,基于锁的比原子的更重。

https://github.com/markwaterman/MutexShootout

这个人对各种互斥体实现进行了比较。原始 Windows SWR 锁是最快的选择,但他们将其与 MSVC 2017 进行比较的最新 std 互斥锁。

我相信这std::shared_mutex是一个 Windows SRW 锁。

您需要性能的最后一小部分吗?然后您应该分析并交换互斥体。如果不是,std::mutex则与最佳选项的百分之十以内,并且可能会继续迭代和支持。

原子整数操作通常比互斥锁便宜,但规则更复杂。此外,原子操作会导致代码中的非本地速度减慢,因为它会导致缓存行被清除以避免其他人获得错误的值。

根据我的经验,在遇到极端情况之前,您可以通过更改算法来获得远远超过 10% 的性能变化。当你真的非常需要性能时,你可能会尽可能地去除互斥体;即使是最快的互斥体对于真正的高性能情况也不够快。

优化是可替代的;当您发现瓶颈时,您可以投入开发精力来提高代码速度。不要编写过早悲观的代码;但是使用 std mutex 相对于替代锁的百分之十的命中通常不足以解决这个问题。


Ayx*_*xan 1

互斥锁很昂贵,就像复制很昂贵一样。这意味着如果您可以消除对副本的需要,那就比必须复制要好。但如果你需要一份副本,那就没有办法了。也同样如此std::mutex。不是因为std::mutex效率低下,而是因为互斥体本质上很昂贵。