【问题标题】:What's the overhead from shared_ptr being thread-safe?shared_ptr 线程安全的开销是多少?
【发布时间】:2014-09-11 06:44:52
【问题描述】:

std::shared_ptr 保证是线程安全的。我不知道典型的实现使用什么机制来确保这一点,但它肯定有一些开销。即使您的应用程序是单线程的,这种开销也会存在。

是上面的情况吗?如果是这样,如果你没有使用线程安全保证,这是否意味着它违反了“你不为你不使用的东西付费”的原则?

【问题讨论】:

标签: c++ multithreading shared-ptr


【解决方案1】:

如果我们查看 cppreference 页面中的 std::shared_ptr,他们会在 实施说明 部分中声明以下内容:

为了满足线程安全要求,引用计数器通常使用std::atomic::fetch_addstd::memory_order_relaxed 递增和递减。

有趣的是要注意一个实际的实现,例如libstdc++ 实现文档here 说:

对于 libstdc++ 中 shared_ptr 的版本,编译器和库 是固定的,这让事情变得更简单:我们有一个原子 CAS 或 我们没有,请参阅下面的锁定政策了解详细信息。

选择锁定策略部分说(强调我的):

有一个 _Sp_counted_base 类,它是一个模板 在枚举 __gnu_cxx::_Lock_policy 上参数化。整个家庭 的类在锁定策略上参数化,直到 __shared_ptr、__weak_ptr 和 __enable_shared_from_this。实际的 std::shared_ptr 类继承自 __shared_ptr 并带有锁定策略 参数根据线程模型自动选择和 为 libstdc++ 配置的平台,以便最好的可用 将使用模板特化。这种设计是必要的,因为 shared_ptr 有一个额外的模板是不符合要求的 参数,即使它具有默认值。可用的政策有:

[...]

3._S_Single

此策略使用无锁定的不可重入 add_ref_lock()。它在构建 libstdc++ 时使用 --enable-threads。

并进一步说(强调我的):

对于所有三个策略,引用计数的递增和递减都是 通过 ext/atomicity.h 中的函数完成,检测程序是否 是多线程的。 如果只存在一个执行线程 程序然后使用更便宜的非原子操作

所以至少在这个实现中,你不需要为你不使用的东西付费。

【讨论】:

  • "memory_order_relaxed 宽松操作:没有同步或排序约束,此操作只需要原子性。"这听起来不错!
【解决方案2】:

至少在 i386 上的 boost 代码中,boost::shared_ptr 是使用原子 CAS 操作实现的。这意味着虽然它有一些开销,但它非常低。我希望std::shared_ptr 的任何实现都是相似的。

在高性能数字代码的紧密循环中,通过切换到原始指针并非常小心,我发现了一些加速。但对于普通代码 - 我不会担心。

【讨论】:

  • 我很确定所有主要的实现都使用了原子。我知道微软就是这样做的。
  • @VioletGiraffe:并非所有硬件平台都支持无锁原子计数器。例如,较旧的 ARM 不会。
  • 原子操作非常昂贵。上次我测量时,我相信在典型的 Intel CPU 上每个原子操作通常约为 20 ns。比常规操作慢 100 倍。
  • “相当昂贵”是相对而言的。任何 I/O 或系统调用通常都会淹没它 - 所以它真的只在紧密循环中很重要。然而,在它回退到互斥锁的旧系统上,你说的是另一个数量级的慢(IIRC 提升曾经做一些非常丑陋的事情来试图降低这个数字。)在这些系统上通常是 shared_ptr 的实现取决于您是否启用了线程标志 - 这导致我在跟踪使用不同标志编译的片段中的错误时遇到各种痛苦(因为shared_ptr 的内部布局发生了变化。)
  • @MikeSeymour "并非所有硬件平台都支持无锁原子计数器" 他们还能实现各种互斥锁和其他 POSIX 线程原语吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-04-15
  • 2013-01-07
  • 1970-01-01
  • 2014-01-06
  • 2019-06-07
相关资源
最近更新 更多