参考
1. 概述
智能指针包括一个实际数据指针和一个引用计数指针,这两个操作不是一个指令可以完成的,因此多线程环境下,势必有问题。
2. shared_ptr 的线程安全
2.1. shared_ptr 的线程安全结论
根据boost官方文档 shared_ptr_thread_safety有如下结论:
- 同一个shared_ptr被多个线程读,是线程安全的;
- 同一个shared_ptr被多个线程写,不是线程安全的;
- 共享引用计数的不同的shared_ptr被多个线程写,是线程安全的。
Examples:
shared_ptr<int> p(new int(42));
Code Example 4. Reading a shared_ptr from two threads,线程安全
// thread A
shared_ptr<int> p2(p); // reads p
// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe
Code Example 5. Writing different shared_ptr instances from two threads,线程安全
// thread A
p.reset(new int(1912)); // writes p
// thread B
p2.reset(); // OK, writes p2
Code Example 6. Reading and writing a shared_ptr from two threads,线程不安全
// thread A
p = p3; // reads p3, writes p
// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write
Code Example 7. Reading and destroying a shared_ptr from two threads,线程不安全
// thread A
p3 = p2; // reads p2, writes p3
// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"
Code Example 8. Writing a shared_ptr from two threads,线程不安全
// thread A
p3.reset(new int(1));
// thread B
p3.reset(new int(2)); // undefined, multiple writes
2.2. shared_ptr 多线程下可能出现的race condition
参考为什么多线程读写 shared_ptr 要加锁?,假设一个shared_ptr的复制分两个步骤:
- 复制ptr 指针
- 复制引用计数指针
考虑一个简单的场景,有 3 个 shared_ptr<Foo> 对象 x、g、n:
shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr
shared_ptr<Foo> x; // 线程 A 的局部变量
shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量
一开始,各安其事。
线程 A 执行
x = g;
(即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。同时线程 B 执行 g = n; (即 write G),两个步骤一起完成了。
这时 Foo1 对象已经销毁,x.ptr 成了空悬指针!
最后回到线程 A,完成步骤 2:
多线程无保护地读写 g,造成了“x 是空悬指针”的后果。这正是多线程读写同一个 shared_ptr 必须加锁的原因。
3. weak_ptr
weak_ptr最初的引入,是为了解决shared_ptr互相引用导致的内存无法释放的问题。weak_ptr不会增加引用计数,不能直接操作对象的内存(需要先调用lock接口),需要和shared_ptr配套使用。
同时,通过weak_ptr获得的shared_ptr可以安全使用,因为其lock接口是原子性的,那么lock返回的是一个新的shared_ptr,不存在同一个shared_ptr的读写操作,除非后续又对这个新的shared_ptr又被其他线程同时读写。