这是一个两部分问题,关于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
的非原子版本? (假设有一个为什么)(如果答案只是“非原子版本从未被考虑过”或“没有人曾要求过非原子版本”那就没关系!)。< / p>
对于问题#2,我想知道是否有人曾提出shared_ptr
的非原子版本(要么是Boost还是标准委员会)(不要替换shared_ptr
的原子版本,但与它并存)并因特定原因被击落。
答案 0 :(得分:96)
1。我想知道是否有非原子版的std :: shared_ptr可用
标准不提供。可能有一个由“第三方”图书馆提供。事实上,在C ++ 11之前,在Boost之前,似乎每个人都编写了自己的引用计数智能指针(包括我自己)。
2。我的第二个问题是为什么不是C ++ 11中提供的std :: shared_ptr的非原子版本?
这个问题在2010年的拉珀斯维尔会议上进行了讨论。该主题由瑞士国家机构评论#20介绍。辩论的双方都有强烈的争论,包括你在问题中提供的论点。但是,在讨论结束时,投票绝大多数(但并非一致)反对添加shared_ptr
的非同步(非原子)版本。
反对的论点包括:
使用未同步的shared_ptr编写的代码最终可能会被用于线程代码中,最终导致难以调试的问题而没有任何警告。
让一个“通用”shared_ptr成为引用计数流量的“单向”有好处:来自the original proposal:
无论使用哪种功能,都具有相同的对象类型,极大地促进了库(包括第三方库)之间的互操作性。
原子的成本虽然不是零,但并不是很重要。通过使用不需要使用原子操作的移动构造和移动分配来减轻成本。此类操作通常用于vector<shared_ptr<T>>
擦除和插入。
没有什么能阻止人们编写他们自己的非原子引用计数智能指针,如果这真的是他们想做的事情。
那天拉珀斯维尔LWG的最后一句话是:
拒绝CH 20.目前尚无达成共识。
答案 1 :(得分:47)
Howard已经很好地回答了这个问题,Nicol对于使用单一标准共享指针类型的好处提出了一些好处,而不是许多不兼容的指针类型。
虽然我完全同意委员会的决定,但我确实认为在特殊情况下使用非同步的shared_ptr
类型有一些好处,所以我调查了这个主题几次。
如果我没有使用多个线程,或者我使用多个线程但是没有跨线程共享指针所有权,那么原子智能指针就会过度。
当你的程序不使用多个线程时,使用GCC,shared_ptr不会使用原子操作来进行引用。这是通过包装函数更新引用计数来完成的,这些函数检测程序是否是多线程的(在GNU / Linux上这只是通过检测程序是否链接到libpthread.so
来完成)并相应地调度到原子或非原子操作
多年前我意识到,因为GCC的shared_ptr<T>
是用__shared_ptr<T, _LockPolicy>
base class实现的,所以即使在多线程代码中也可以使用单线程锁定策略,通过显式使用__shared_ptr<T, __gnu_cxx::_S_single>
。不幸的是,因为那不是一个预期的用例,它在GCC 4.9之前并没有完全发挥作用,并且一些操作仍然使用包装器函数,因此即使您已明确请求_S_single
策略,也会调度到原子操作。有关更多详细信息,请参阅http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html处的第(2)点和GCC的补丁,以便即使在多线程应用程序中也可以使用非原子实现。我多年来一直坐在那个补丁上,但我最终为GCC 4.9提交了它,它允许你使用这样的别名模板来定义一个非线程安全的共享指针类型,但速度稍快:
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>
是shared_ptr<T>
的别名,那么使用正确的预处理程序会破坏您的代码仍可以正常使用其他实现。对于GCC,它会更快一点。
<子>
如果您在4.9之前使用GCC,则可以通过将_Sp_counted_base<_S_single>
显式特化添加到您自己的代码中来使用它(并确保没有实例化__shared_ptr<T, _S_single>
而不包括特化,以避免ODR违规。) std
类型的这种特化在技术上是未定义的,但在实践中可行,因为在这种情况下,我将特化添加到GCC或将它们添加到您自己的代码之间没有区别。
子>
答案 2 :(得分:22)
我的第二个问题是为什么不是C ++ 11中提供的std :: shared_ptr的原子版本? (假设有原因)。
人们可以很容易地问为什么没有侵入式指针,或者可能有任何其他可能的共享指针变体。
Boost传承的shared_ptr设计旨在创建智能指针的最低标准语言。一般来说,你可以把它从墙上拉下来并使用它。它可以在各种应用程序中普遍使用。你可以把它放在一个界面中,而且好的人会乐意使用它。
线程只会让更多在将来流行。实际上,随着时间的推移,线程通常是实现性能的主要手段之一。要求基本智能指针完成支持线程所需的最低限度,这有助于实现这一目标。
将六个智能指针与它们之间的微小差异转移到标准中,甚至更糟糕的是基于策略的智能指针,这将是非常可怕的。每个人都会选择他们最喜欢的指针并放弃所有其他指针。没有人能够与其他任何人沟通。它就像C ++字符串的当前情况,每个人都有自己的类型。更糟糕的是,因为与字符串的互操作比智能指针类之间的互操作容易得多。
Boost,以及委员会的扩展,选择了一个特定的智能指针来使用。它提供了良好的功能平衡,并在实践中广泛使用。
在某些极端情况下, std::vector
与裸阵列相比效率低下。它有一些局限性;某些用途确实希望对vector
的大小有一个硬性限制,而不使用throw分配器。但是,委员会没有将vector
设计成适合每个人的一切。它被设计为大多数应用程序的良好默认值。那些无法工作的人可以写一个满足他们需求的替代方案。
就像你可以使用智能指针一样,如果shared_ptr的原子性是一个负担。然后,人们也可能会考虑不要将它们复制到那么多。
答案 3 :(得分: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的笔记本。这是以秒为单位的时间:
test run setup boost(1.49) std with make_shared modified boost mt-unsafe(11) 11.9 9/11.5(-pthread on) 8.4 atomic(11) 13.6 12.4 13.0 mt-unsafe(12) 113.5 85.8/108.9(-pthread on) 81.5 atomic(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可能更好。
答案 4 :(得分:1)
Boost提供了一个非原子的shared_ptr
。它称为local_shared_ptr
,可以在boost的智能指针库中找到。