谈论shared_ptr的线程安全性

本文探讨了在多线程环境中使用shared_ptr的安全性问题,包括读写操作的线程安全性和如何避免死锁。通过具体示例展示了正确的读写实现方式,以及常见的错误写法可能导致的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 一个shared_ptr对象可以同时被多个线程同时读取
  • 两个shared_ptr对象实体可以被两个线程同时写入,“析构”算写操作
  • 如果要从多个线程读写同一个shared_ptr对象,那么需要加锁
    从这个角度来看,shared_ptr 的线程安全级别和标准库容器差不多,所以在多线程中同时访问一个shared_ptr,象,正确做法是用mutex保护,并且尽量保证临界区范围小

shared_ptr是引用计数智能指针,如果当前只有一个观察者,那么引用计数的值就是1
对于write端,如果发现它的引用计数为1,那么可以安全的修改共享对象,不必担心有人读取它
对于read端,在读之前把引用计数加1,读完之后减1,这样保证在读的期间引用计数大于1,可以阻止读时写操作
那么,问题就在对于write端,如果发现引用计数大于1,该如何处理写,明确知道此时在读数据。sleep等待?
举一个简单的例子

typedef std::vector<Foo> FooList;
typedef std::shared_ptr<FooList> FooListPtr;
MutexLock mutex;
FooListPtr g_foos;

//读端
void traverse()
{
    FooListPtr foos;
    {
        MutexLockGuard lock(mutex);
        foos = g_foos;
        assert(!g_foos.unique());
    }
    for (auto iter = foos->begin(); iter != foos.end(); ++iter)
        iter->doit();
}

//写端
void post(const Foo& f)
{
    MutexLockGuard lock(mutex);
    if (!g_foos.unique())//如果正在写,说明有两个引用计数
    {
        g_foos.reset(new FooList(*g_foos));
    } 
    assert(g_foos.unique());
    g_foos->push_back(f);
}

上面例子,在写端,当有两个引用计数时,我们不能修改,而是复制一份,在副本上修改,这样子避免死锁
考虑下面几种错误的write端写法

void post(const Foo& f)
{
    MutexLockGuard lock(mutex);
    g_foos->push_back(f);//很明显,在读端的iter->doit()阶段,如果push_back,那么迭代器会失效
}

void post(const Foo& f)
{
    FooListPtr newFoos(new FooList(*g_foos));
    newFoos->push_back(f);
    MutexLockGuard lock(mutex);
    g_foos = newFoos;//两个线程在write期间,g_foos可能只添加了一个f
}

void post(const Foo& f)
{
    FooListPtr oldFoos;
    {
        MutexLockGuard lock(mutex);
        oldFoos = g_foos;
    }
    FooListPtr newFoos(new FooList(*oldFoos));
    newFoos->push_back(f);
    MutexLockGuard lock(mutex);
    g_foos = newFoos;//两个线程在write期间,g_foos可能只添加了一个f
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值