shared_ptr在多线程下的安全性问题

发布时间 2023-09-20 10:26:05作者: ImreW

1. 引用

boost官方文档中有如下结论:

https://www.boost.org/doc/libs/1_57_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety

1)同一个shared_ptr被多个线程“读”是安全的;

2)同一个shared_ptr被多个线程“写”是不安全的;

3)共享引用计数的不同的shared_ptr被多个线程”写“ 是安全的;

  1.  A shared_ptr instance can be "read" (accessed using only const operations) simultaneously by multiple threads.
  2. Different shared_ptr instances can be "written to" (accessed using mutable operations such as operator= or reset) simultaneously by multiple threads (even when these instances are copies, and share the same reference count underneath.)
  3. Any other simultaneous accesses result in undefined behavior.

2. 线程安全

shared_ptr指针类有两个成员变量,一个是指向变量的指针;一个是资源被引用的次数,引用次数加减操作内部自动加锁解锁,是线程安全的。

2.1 引用计数

虽然引用计数存在于每一个shared_ptr对象中,但是实际上它是要跟随对象所管理的资源。引用计数会随着指向这块资源的shared_ptr对象的增加而增加。因此引用计数是要指向同一块资源的所有的对象共享的,所以实际上引用计数在shared_ptr底层中是以指针的形式实现的,所有的对象通过指针访问同一块空间,从而实现共享。

那么也就是说,引用计数是一个临界资源,所以在多线程中,我们必须要保证临界资源访问的安全性,因此在shared_ptr底层中在对引用计数进行访问之前,首先对其加锁,当访问完毕之后,在对其进行解锁。

所以shared_ptr的引用计数是线程安全的

2.2 被shared_ptr对象所管理的资源

shared_ptr对象所管理的资源存放在堆上,它可以由多个shared_ptr所访问,所以这也是一个临界资源。因此当多个线程访问它时,会出现线程安全的问题

3. 一个例子

shared_ptr发生拷贝的流程:

1)拷贝智能指针指向的资源(非原子操作)

2)增减引用计数(原子操作)

假如有下面三个同类型的shared_ptr:

shared_ptr<foo> p1;                 //线程A的局部变量
shared_ptr<foo> p2(new foo);        //线程A和线程B所共享
shared_ptr<foo> p3(new foo);        //线程B的局部变量

1)一开始他们之间的关系可以用下图来表示:

2)然后线程A先执行语句:p1=p2在执行这条语句时,先改变ptr的指向,然后才修改引用计数。因为现在是多线程,所以很可能出现这样的情况:在线程A执行完步骤一时,还没来得及执行步骤二,就轮到线程B来执行。如下图所示:

3)现在线程B开始执行p2=p3,并且没有被打断,也就是说步骤一二都完成。
先是步骤一:

然后步骤二:

注意此时因为第一个资源的引用计数已经为0,所以会销毁该资源,也就是说,步骤二执行完之后,p1的ptr是一个悬空指针

所以多个shared_ptr对象对其所管理的资源的访问不是线程安全的。如果不使用锁这会造成线程安全问题。

结论:所以当我们多个线程访问同一个shared_ptr时,应该要进行加锁操作。