104

这是一个由两部分组成的问题,所有关于 的原子性std::shared_ptr

1. 据我所知,std::shared_ptr它是唯一的智能指针,<memory>它是原子的。我想知道是否有可用的非原子版本std::shared_ptr(我在 中看不到任何东西<memory>,所以我也愿意接受标准之外的建议,比如 Boost 中的建议)。我知道boost::shared_ptr也是原子的(如果BOOST_SP_DISABLE_THREADS没有定义),但也许还有另一种选择?我正在寻找与 具有相同语义std::shared_ptr但没有原子性的东西。

2.我明白为什么std::shared_ptr是原子的;这有点好。然而,它并不适用于所有情况,C++ 历来有“只为你使用的东西付费”的口头禅。如果我没有使用多个线程,或者如果我使用多个线程但没有跨线程共享指针所有权,那么原子智能指针就过分了。我的第二个问题是为什么C++11中没有提供非原子版本std::shared_ptr?(假设有一个原因)(如果答案只是“根本没有考虑过非原子版本”或“从来没有人要求过非原子版本”,那很好!)。

对于问题 #2,我想知道是否有人曾经提出过shared_ptr(对于 Boost 或标准委员会)的非原子版本(不是要替换 的原子版本shared_ptr,而是与之共存)并且它被否决了具体原因。

4

5 回答 5

117

1. 我想知道是否有非原子版本的 std::shared_ptr 可用

标准未提供。“第 3 方”库很可能提供了一个。事实上,在 C++11 和 Boost 之前,似乎每个人都编写了自己的引用计数智能指针(包括我自己)。

2.我的第二个问题是为什么C++11中没有提供std::shared_ptr的非原子版本?

这个问题在 2010 年的 Rapperswil 会议上进行了讨论。该主题是由瑞士的国家机构评论 #20 提出的。辩论的双方都有强烈的论据,包括你在问题中提供的论据。然而,在讨论结束时,以压倒性多数(但不是一致)反对添加不同步(非原子)版本的shared_ptr.

反对的论据包括:

  • 使用不同步的 shared_ptr 编写的代码最终可能会在以后的线程代码中使用,最终导致难以调试的问题而没有警告。

  • 拥有一个“通用”shared_ptr,它是引用计数流量的“一种方式”,它有好处:来自原始提案

    无论使用何种功能,都具有相同的对象类型,极大地促进了库之间的互操作性,包括第三方库。

  • 原子的成本虽然不是零,但并不是压倒性的。通过使用不需要使用原子操作的移动构造和移动分配来减轻成本。此类操作常用于vector<shared_ptr<T>>擦除和插入。

  • 没有什么能阻止人们编写自己的非原子引用计数智能指针,如果这确实是他们想要做的。

那天在拉珀斯维尔的 LWG 的最后一句话是:

拒绝 CH 20。目前没有共识做出改变。

于 2013-02-28T16:10:02.003 回答
56

Howard 已经很好地回答了这个问题,Nicol 提出了一些关于使用单一标准共享指针类型而不是许多不兼容的共享指针类型的好处。

虽然我完全同意委员会的决定,但我确实认为在特殊情况下shared_ptr使用类非同步类型有一些好处,所以我已经研究了几次这个话题。

如果我没有使用多个线程,或者如果我使用多个线程但没有跨线程共享指针所有权,那么原子智能指针就过分了。

当您的程序不使用多个线程时,使用 GCC shared_ptr 不使用原子操作作为引用计数。这是通过检测程序是否为多线程的包装函数更新引用计数来完成的(在 GNU/Linux 上,这是通过检查 Glibc 中的一个特殊变量来完成的,该变量表示程序是否为单线程[1])并分派给 atomic或相应的非原子操作。

多年前我意识到,由于 GCCshared_ptr<T>是根据__shared_ptr<T, _LockPolicy>基类实现的,因此即使在多线程代码中,也可以通过显式使用__shared_ptr<T, __gnu_cxx::_S_single>. 您可以使用这样的别名模板来定义不是线程安全的共享指针类型,但速度稍快[2]

template<typename T>
  using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;

这种类型不能互操作,std::shared_ptr<T>并且只有在保证shared_ptr_unsynchronized对象永远不会在没有用户提供的额外同步的情况下在线程之间共享时才能安全使用。

这当然是完全不可移植的,但有时没关系。shared_ptr_unsynchronized<T>使用正确的预处理器 hack,如果是 的别名,您的代码仍然可以在其他实现中正常工作shared_ptr<T>,使用 GCC 会快一点。


[1] 在 Glibc 2.33 添加该变量之前,包装函数将检测程序是否链接到libpthread.so,作为检查单线程与多线程的不完美方法。

[2] 不幸的是,因为这不是一个预期的用例,它在 GCC 4.9 之前并没有以最佳方式工作,即使您明确请求了 `_S_single` 策略,一些操作仍然使用包装函数,因此被分派给原子操作. 请参阅http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html上的第 (2) 点,了解更多详细信息和 GCC 补丁,以允许即使在多线程应用程序中也可以使用非原子实现。我在那个补丁上坐了很多年,但我最终为 GCC 4.9 提交了它。
于 2013-02-28T17:26:55.400 回答
21

我的第二个问题是为什么 C++11 中没有提供 std::shared_ptr 的非原子版本?(假设有一个原因)。

人们可以很容易地问为什么没有侵入式指针,或者共享指针的任何其他可能变体。

从 Boost 传下来的 设计shared_ptr一直是创建智能指针的最低标准通用语言。一般来说,你可以把它从墙上拉下来并使用它。它可以在各种应用程序中普遍使用。你可以把它放在一个界面中,很可能好人会愿意使用它。

线程只会在未来变得更加普遍。事实上,随着时间的推移,线程通常将成为实现性能的主要手段之一。要求基本智能指针完成支持线程所需的最低限度的工作有助于实现这一现实。

将六个具有微小差异的智能指针转储到标准中,或者更糟糕的是基于策略的智能指针,将会很糟糕。每个人都会选择他们最喜欢的指针并放弃所有其他指针。没有人能够与其他人交流。这就像 C++ 字符串的当前情况,每个人都有自己的类型。更糟糕的是,因为与字符串的互操作比智能指针类之间的互操作要容易得多。

Boost,以及委员会的扩展,选择了一个特定的智能指针来使用。它提供了良好的功能平衡,并在实践中被广泛和普遍使用。

std::vector在某些极端情况下,与裸数组相比也有一些低效率。它有一些限制;有些用途真的想对 a 的大小有一个硬限制vector,而不使用抛出分配器。然而,委员会并没有设计vector成适合所有人的一切。它被设计为大多数应用程序的良好默认设置。那些无法使用它的人可以编写一个适合他们需要的替代方案。

shared_ptr如果' 的原子性是一种负担,就像智能​​指针一样。再说一次,人们也可能会考虑不要过多地复制它们。

于 2013-02-28T07:14:38.263 回答
5

Boost 提供了一个shared_ptr非原子的。它叫做local_shared_ptr,可以在 boost 的智能指针库中找到。

于 2019-01-02T11:25:19.940 回答
4

我正在准备工作中关于 shared_ptr 的演讲。我一直在使用修改后的 boost shared_ptr,避免使用单独的 malloc(就像 make_shared 可以做的那样)和用于锁定策略的模板参数,比如上面提到的 shared_ptr_unsynchronized。我正在使用该程序

http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html

作为测试,在清理了不必要的 shared_ptr 副本之后。该程序仅使用主线程并显示测试参数。测试环境是一个运行 linuxmint 14 的笔记本。这里是以秒为单位的时间:

测试运行设置 boost(1.49) 标准与 make_shared 修改提升
mt-unsafe(11) 11.9 9/11.5(-pthread on) 8.4  
原子(11) 13.6 12.4 13.0  
mt-unsafe(12) 113.5 85.8/108.9(-pthread on) 81.5  
原子(12) 126.0 109.1 123.6  

只有 'std' 版本使用 -std=cxx11,并且 -pthread 可能会在 g++ __shared_ptr 类中切换 lock_policy。

从这些数字中,我看到了原子指令对代码优化的影响。测试用例不使用任何 C++ 容器,但vector<shared_ptr<some_small_POD>>如果对象不需要线程保护,则可能会受到影响。Boost 受到的影响较小,因为额外的 malloc 限制了内联和代码优化的数量。

我还没有找到一台具有足够内核来对原子指令的可扩展性进行压力测试的机器,但仅在必要时使用 std::shared_ptr 可能会更好。

于 2013-05-30T04:44:51.137 回答