C++中shared_ptr的计数对象是原子的,但是有一个问题,就是在析构之前shared_ptr调用父类__shared_ptr的析构,__shared_ptr有一个__shared_count成员,这个成员的析构里面调用了其指针成员_Sp_counted_base的方法_M_release,而这个_M_release里面则调用__gnu_cxx::__exchange_and_add_dispatch方法对计数进行-1操作,如果原来的计数为1那么就调用_M_dispose方法将对象执行删除了。在判断当前计数为1后,_M_dispose调用之前,如果当前线程挂起,然后有另外一个线程调用拷贝构造函数对该shared_ptr进行拷贝,则会调用父类__shared_ptr的拷贝构造(也是default),触发调用__shared_ptr的成员__shared_counter的复制构造。__shared_counter的复制构造会调用指针成员_Sp_counted_base的_M_add_ref_copy方法,这个方法对引用次数_M_use_count成员进行原子+1的操作。那么这个时候两个类指向共同的__Sp_counted_base对象,这个对象此时的计数又从0变成了1!!!接着,原来的线程被唤醒调用_M_dispose函数对指向的对象进行析构,然后在判断_M_weak_count后调用_M_destroy方法对自己本身进行析构!!!于是新拷贝而成的shared_ptr指针对象指向了空悬的指针,再次调用出现未定义行为!!!
由这个过程,我们可以得到shared_ptr在多线程环境下的一个使用特点,那就是在多线程运行的情况下对于某个指针的引用次数不能增,只能减,也即,我们不能在使用shared_ptr的线程的函数栈未回退的情况下对指向同一个对象的智能指针执行拷贝/拷贝赋值操作,至少在Linux环境下是不行的,但是可以调用reset放弃当前正在使用的智能指针。有一种情况是可行的,因为该种冲突只发生在引用计数到1的情况下,那么可以保证引用计数不为1。就比如,在线程运行期间,可以在外部保存一个一直不会被析构的指向相同对象的shared_ptr。这种情况下,由于引用计数是原子的,因此引用计数数量不会有错,那么在所有线程都不再引用之后就可以对其进行唯一的依次析构了,而在线程运行期间可以自由地对指向相同对象的shared_ptr进行复制操作。
shared_ptr多线程安全性
于 2024-01-12 14:56:43 首次发布