boost shared_ptr <XXX> 是否线程安全?

32

我有一个关于 boost::shared_ptr<T> 的问题。

有很多线程。

using namespace boost;

class CResource
{
  // xxxxxx
}

class CResourceBase
{
public:
   void SetResource(shared_ptr<CResource> res)
   {
     m_Res = res;
   }

   shared_ptr<CResource> GetResource()
   {
      return m_Res;
   }
private:
   shared_ptr<CResource> m_Res;
}

CResourceBase base;

//----------------------------------------------
// Thread_A:
    while (true)
    {
       //...
       shared_ptr<CResource> nowResource = base.GetResource();
       nowResource.doSomeThing();
       //...
    }

// Thread_B:
    shared_ptr<CResource> nowResource;
    base.SetResource(nowResource);
    //...

Q1

如果Thread_A不关心nowResource是否是最新的,那么这段代码会出现问题吗?

我的意思是当Thread_B没有完全执行SetResource()时,Thread_A通过GetResource()获取到一个错误的智能指针吗?

Q2

什么是线程安全?

如果我不关心资源是否是最新的,那么shared_ptr<CResource> nowResource会在nowResource被释放时崩溃程序,还是会破坏shared_ptr<CResource>本身?

5个回答

40

boost::shared_ptr<> 提供一定级别的线程安全性。引用计数以线程安全的方式进行操作(除非你配置了boost禁用线程支持)。

所以你可以在不同线程之间复制一个 shared_ptr,并且引用计数会被正确地维护。但是,你不能安全地在多个线程中修改实际的shared_ptr对象实例本身(例如从多个线程调用reset())。因此,在你的使用中是不安全的——你正在从多个线程中修改实际的 shared_ptr 实例,因此需要自己保护。

在我的代码中,shared_ptr通常是局部变量或按值传递的参数,因此不存在问题。我通常使用线程安全队列将其从一个线程传输到另一个线程。

当然,这并没有解决访问由shared_ptr指向的对象的线程安全性——这也取决于你自己。


不,他没有在不同的线程中修改shared_ptr实例!只有B线程会修改它。 - Zach Saw

33

来自Boost documentation

shared_ptr对象提供与内置类型相同的线程安全级别。一个shared_ptr实例可以被多个线程同时“读取”(只使用const操作进行访问)。不同的shared_ptr实例可以被多个线程同时“写入”(使用可变操作如operator=或重置进行访问)(即使这些实例是副本并且在底层共享相同的引用计数时)。

任何其他同时访问都会产生未定义行为。

因此,你的用法不安全,因为它同时读取和写入m_res。 Boost文档中的示例3也说明了这一点。

你应该使用一个单独的mutex来保护对SetResource/GetResourcem_res的访问。


-1 他的用法并不违反你从boost文档中引用的内容。事实上,这正是boost文档所说的。 - Zach Saw
3
@ZachSaw:我稍微扩展了一下我的回答。他的用法不安全,因为他同时读写同一个 shared_ptr<> 实例。 - sth
@sth:你是对的:m_res被不同的线程读取和写入。我曾经遇到过这种情况,如果没有锁定,程序会崩溃。这是Boost文档中“线程安全”部分的第3个示例。 - gast128

3

好的,tr1::shared_ptr(基于boost)的文档告诉了我们不同的故事,它暗示资源管理是线程安全的,而对资源的访问则不是。

"...

线程安全性

C++0x-only特性包括:rvalue-ref/move支持、分配器支持、别名构造函数、make_shared和allocate_shared。此外,在C++0x模式下,接受auto_ptr参数的构造函数已被弃用。

Boost shared_ptr文档中的线程安全性部分说“shared_ptr对象提供与内置类型相同级别的线程安全性”。实现必须确保对单独的shared_ptr实例的并发更新是正确的,即使这些实例共享引用计数,例如:

shared_ptr a(new A); shared_ptr b(a);

// 线程1 // 线程2

a.reset(); b.reset();

动态分配的对象必须由其中一个线程销毁。弱引用使事情更加有趣。用于实现shared_ptr的共享状态必须对用户透明,并且必须始终保留不变性。共享状态的关键部分是强引用计数和弱引用计数。这些的更新需要是原子的,并且可见于所有线程,以确保正确清理所管理的资源(这毕竟是shared_ptr的工作!)。在多处理器系统上,可能需要内存同步,以使引用计数更新和所管理的资源的销毁无竞争。

..."

请参见http://gcc.gnu.org/onlinedocs/libstdc++/manual/memory.html#std.util.memory.shared_ptr


“嗯,tr1::shared_ptr(它基于boost)的文档讲述了不同的故事,这意味着资源管理是线程安全的,而对于资源的访问则不是。”我倾向于同意这一点,因为shared_ptr最多只能保证在存在线程的情况下,引用计数对资源进行一致的维护。如果您更改资源值,则它指向的实际资源必须自行具备线程安全性。 - Ghita

1

m_Res 不是线程安全的,因为它同时进行读写操作, 您需要使用 boost::atomic_store/load 函数来保护它。

//--- Example 3 ---
// thread A
p = p3; // reads p3, writes p
// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write

-1

你的类存在循环引用的情况;shared_ptr<CResource> m_Res 不能成为 CResourceBase 的成员。你可以使用 weak_ptr 替代。


1
为什么_shared_ptr<CResource> m_Res不能成为CResourceBase的成员? - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接