【问题标题】:Why mutex (std::mutex) is heavy?为什么互斥锁(std::mutex)很重?
【发布时间】:2021-06-04 22:57:10
【问题描述】:

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

【问题讨论】:

  • 你能举一个例子,有人说互斥锁“很重”吗?我们在这里缺乏关于他们试图做什么以及替代方案是什么的背景。
  • 重于什么?在什么情况下?如果您只想增加一个整数,则可以使用atomic<int> 更好地为您服务。
  • std::mutex 可以被认为很重,因为它通常使用系统调用来实现,而 std::atomic 则没有。
  • 与“没有互斥锁”相比,互斥锁“重”,但如果你需要一个,你就需要一个,没有办法。如果您可以重组以避免它们并且代码仍然正确,那么恭喜。如果没有,这些成本很小,很烦人,但很有必要。
  • 我使用std::mutex,直到我确定这是分析后的性能问题。然后我会考虑诸如std::atomic 之类的替代方案,如果付出的努力(以及更棘手、更难维护的代码)是否值得性能提升。但我发现一个更好的通用方法是尝试所有跨线程对象的不变性,那么这不是问题。

标签: c++ multithreading c++11 boost mutex


【解决方案1】:

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

https://github.com/markwaterman/MutexShootout

此人对各种互斥体实现进行了比较。原始 Windows SWR 锁是最快的选择,但他们与之比较的最新标准互斥锁是 MSVC 2017 的。

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

您是否需要每一个微小的性能百分比?然后你应该分析和交换互斥锁。如果不是,std::mutex 与最佳选项的差距在 10% 以内,并且可能会继续得到迭代和支持。

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

根据我的经验,在遇到极端情况之前,您可以通过更改算法来获得超过 10% 的性能更改。当你真的非常需要性能时,无论如何你可能会尽可能多地去除互斥锁;即使是最快的互斥体也无法满足真正高性能的情况。

优化是可替代的;当您发现瓶颈时,您可以花费您的开发精力来更快地编写代码。不要编写过早悲观的代码;但是在替代锁上使用 std 互斥锁 10% 的命中率通常不足以解决这个问题。

【讨论】:

    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

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

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

      话虽如此,这样的想法会让您深入了解专家级并发编程。除非您有需要担心诸如滚动自己的锁定之类的微优化的特定问题,否则std::mutex 是可行的方法,并且它的开销在它正在做的事情的合理范围内很好。

      【讨论】:

        【解决方案4】:

        互斥量很昂贵,就像复制很昂贵一样。这意味着如果您可以避免复制的需要,那比必须复制要好。但是,如果您需要副本,则无法解决。 std::mutex 也是如此。不是因为std::mutex 效率低下,而是因为互斥体本身就很昂贵。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-02-15
          • 2013-02-02
          • 2011-02-24
          • 2021-10-30
          • 2010-11-18
          • 2014-02-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多