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



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

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

如今,在许多操作系统中,互斥被优化为在发生争用之前不会进入内核。例如,在Linux中,它是使用futex("快速用户空间互斥")在Windows-SRW锁中实现的。然而,一旦发生争用,就会进入内核。一旦一个线程需要等待,它将是";进入睡眠状态";并且在锁被释放的时刻和线程被调度再次执行的时刻之间将存在显著的延迟。

如果需要同步,有时在简单的atomic上循环就足够了。如果争论是罕见和短暂的,那么你可以用一个"旋转锁定";,即循环直到满足某个条件。即使循环10000次,它也可能比单个系统调用更快。

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

std::mutex被设计成一个轻量级的可移植包装器,它围绕着操作系统的本地互斥设备。如果您的目标是调用这些设施,那么mutex只引入了一个非常微不足道的开销,而不是直接调用OS名称的API。

然而,根据您的使用情况,使用操作系统设施可能不是最佳解决方案。例如,为了保护数据不受并发访问,您还可以使用std::atomic等较低级别的原语编写自己的锁。然而,这将是一种不同类型的锁定算法。特别是,如果不能立即获得互斥,std::mutex将使一个等待线程进入睡眠状态,这是在没有与操作系统交谈的情况下无法做到的。不过,在某些情况下,这样一个更简单的锁定算法就足以完成任务。这里的一个流行例子是,锁争用预计只在极少数情况下发生。

话虽如此,这样的想法可以让您深入到专家级并发编程中。除非您有特定的问题需要担心微优化,比如滚动自己的锁定,否则std::mutex是可行的,它的开销在它所做的事情的合理范围内。

所有类型的同步都是"重";,并且基于锁的比原子的重。

https://github.com/markwaterman/MutexShootout

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

我相信std::shared_mutex是发动机罩下的车窗SRW锁。

你需要性能的最后一小部分吗?然后您应该分析并交换互斥对象。如果不是,std::mutex在最佳选项的10%以内,并且可能会继续迭代和支持。

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

根据我的经验,在达到极端情况之前,你可以进行算法更改,以获得远远超过10%的性能更改。当你真的、真的需要性能时,你可能会尽可能多地剥离互斥体;即使是最快的互斥锁,对于真正高性能的情况也不够快。

优化是可替代的;当您发现瓶颈时,您可以将开发工作花在更快地编写代码上。不要过早地编写令人讨厌的代码;但是,在替代锁上使用std互斥锁的命中率通常不足以达到10%。

互斥是昂贵的,正如复制是昂贵的一样。这意味着,如果你能避免复制的需要,那就比必须复制要好。但如果你需要一份复印件,那就没有办法了。std::mutex也是如此。不是因为std::mutex效率低下,而是因为互斥锁本身就很昂贵。

最新更新