- 一个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
}