完全线程安全的shared_ptr实现

时间:2009-01-14 03:28:42

标签: c++ boost thread-safety shared-ptr

有人知道完全线程安全的shared_ptr实现吗?例如。增强shared_ptr的实现对于目标(引用计数)是线程安全的,对于同时shared_ptr实例读取也是安全的,但不是写入或读/写。

(见Boost docs,例子3,4和5)。

是否存在对shared_ptr个实例完全线程安全的shared_ptr实现?

奇怪的是,提升文档说:

  

shared_ptr对象提供与内置类型相同的线程安全级别。

但是如果将普通指针(内置类型)与smart_ptr进行比较,则同时写入普通指针是线程安全的,但同时写入smart_ptr不是。< / p> 编辑:我指的是x86架构上的无锁实现。

EDIT2:这种智能指针的一个示例用例是有许多工作线程,它们使用当前工作项更新全局shared_ptr,并使用监视器线程获取工作项的随机样本。 shared-ptr将拥有该工作项,直到为其分配另一个工作项指针(从而销毁先前的工作项)。监视器将获得工作项的所有权(从而防止工作项被销毁),方法是将其分配给自己的shared-ptr。它可以通过XCHG和手动删除完成,但如果共享ptr可以做到这一点会很好。

另一个例子是全局shared-ptr持有“处理器”,由某个线程分配,并由其他一些线程使用。当“用户”线程看到处理器shard-ptr为NULL时,它使用一些替代逻辑来进行处理。如果它不是NULL,它会通过将处理器分配给它自己的shared-ptr来防止处理器被破坏。

9 个答案:

答案 0 :(得分:18)

为这种完全线程安全的shared_ptr实现添加必要的障碍可能会影响性能。考虑下面的比赛(注意:伪代码比比皆是):

主题1:     global_ptr = A;

主题2:     global_ptr = B;

主题3:     local_ptr = global_ptr;

如果我们将其分解为其组成部分:

主题1:

A.refcnt++;
tmp_ptr = exchange(global_ptr, A);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

主题2:

B.refcnt++;
tmp_ptr = exchange(global_ptr, B);
if (!--tmp_ptr.refcnt) delete tmp_ptr;

主题3:

local_ptr = global_ptr;
local_ptr.refcnt++;

显然,如果线程3在A的交换之后读取指针,那么B会在引用计数递增之前删除它,会发生不好的事情。

要处理这个问题,我们需要在线程3执行refcnt更新时使用虚拟值: (注意:compare_exchange(variable,expected,new)原子地将变量中的值替换为new,如果它当前等于new,则如果成功则返回true)

主题1:

A.refcnt++;
tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

主题2:

B.refcnt++;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, A))
    tmp_ptr = global_ptr;
if (!--tmp_ptr.refcnt) delete tmp_ptr;

主题3:

tmp_ptr = global_ptr;
while (tmp_ptr == BAD_PTR || !compare_exchange(global_ptr, tmp_ptr, BAD_PTR))
    tmp_ptr = global_ptr;
local_ptr = tmp_ptr;
local_ptr.refcnt++;
global_ptr = tmp_ptr;

你现在必须在/ read / operation中间添加一个带有atomics的循环。这不是一件好事 - 在一些CPU上它可能非常昂贵。更重要的是,你也在忙着等待。你可以开始聪明地使用futexes和诸如此类的东西 - 但是到那时你已经重新发明了锁。

这个成本必须由每个操作承担,并且在本质上与锁给你的方式非常相似,这就是为什么你通常看不到这样的线程安全的shared_ptr实现。如果您需要这样的东西,我建议将mutex和shared_ptr包装到一个便捷类中以自动锁定。

答案 1 :(得分:2)

同时写入内置指针肯定不是线程安全的。考虑一下如果你真的想让自己疯狂的话,写一个关于内存障碍的相同值的含义(例如,你可能有两个线程认为同一个指针有不同的值)。

RE:注释 - 内置输入不是双重删除的原因是因为它们根本没有删除(并且我使用的boost :: shared_ptr的实现不会双重删除,因为它使用特殊的原子增量并且减少,所以它只会单删除,但结果可能有一个指针和另一个的引用计数。或者几乎任何两者的组合。这将是坏的。)。 boost文档中的语句是正确的,您可以获得与内置文件相同的保证。

RE:EDIT2 - 您描述的第一种情况在使用内置函数和shared_ptrs时非常不同。在一个(XCHG和手动删除)中没有引用计数;当你这样做时,你假设你是唯一的所有者。如果使用共享指针,则表示其他线程可能拥有所有权,这使得事情变得更加复杂。我相信这可以通过比较和交换,但这将非常不便携。

C ++ 0x推出了一个原子库,这使得编写通用的多线程代码变得更加容易。您可能需要等到出现才能看到线程安全智能指针的良好跨平台参考实现。

答案 2 :(得分:1)

我不知道这样的智能指针实现,但我不得不问:这种行为怎么会有用呢?我能想到的唯一可以找到同时指针更新的场景是竞争条件(即错误)。

这不是批评 - 可能有一个合法的用例,我根本想不到它。请让我知道!

回复:EDIT2 感谢您提供一些方案。听起来像原子指针写入在这些情况下会很有用。 (一件小事:对于第二个例子,当你写“如果它不是NULL,它通过将处理器分配给它自己的shared-ptr来防止处理器被破坏”,我希望你的意思是你将全局共享指针分配给它本地共享指针首先然后检查本地共享指针是否为NULL - 您所描述的方式容易出现竞争条件,其中全局共享指针在您测试它之后和分配之前变为NULL到当地的。)

答案 3 :(得分:0)

您可以使用此实现Atomic Reference Counting Pointers来至少实现引用计数机制。

答案 4 :(得分:-1)

您的编译器可能已经在较新的C ++标准中提供了线程安全的智能指针。我相信TBB正在计划添加一个智能指针,但我认为它还没有包含在内。但是,您可以使用TBB的一个线程安全容器。

答案 5 :(得分:-1)

您可以通过在每个共享指针中包含一个互斥对象,并使用该锁包装递增/递减命令来轻松完成此操作。

答案 6 :(得分:-1)

我不认为这很容易,用sh包装你的sh_ptr类是不够的。确实,如果为所有共享指针维护一个CS,它可以确保避免在不同线程之间相互访问和删除sh_ptr对象。但这会很糟糕,每个共享指针的一个CS对象将是一个真正的瓶颈。如果每个可压缩的新ptr -s都具有不同的CS',那么这将是合适的,但是这样我们应该创建我们的CS,并确保sh_ptr类的副本传输这个共享的Cs。现在我们遇到了同样的问题:谁会保证这个Cs ptr已被删除。我们可以使用每个实例的易失性m_bReleased标志更加智能,但这样我们就不会在检查标志和使用共享C之间遇到安全漏洞。我看不出这个问题的完全安全解决方案。也许那个糟糕的全球Cs会成为杀死应用程序的次要坏事。 (对不起我的英文)

答案 7 :(得分:-1)

这可能不是您想要的,但boost::atomic文档提供了如何使用intrusive_ptr原子计数器的示例。 intrusive_ptr是Boost智能指针之一,它执行“侵入式引用计数”,这意味着计数器“嵌入”在目标中,而不是由智能指针提供。

提升atomic使用示例:

http://www.boost.org/doc/html/atomic/usage_examples.html

答案 8 :(得分:-3)

在我看来,最简单的解决方案是使用intrusive_ptr进行一些次要(但必要的)修改。

我在下面分享了我的实现:

http://www.philten.com/boost-smartptr-mt/