谈论shared_ptr的线程安全性

  • 一个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
}
相关推荐
©️2020 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页