參考:
https://blog.csdn.net/d_guco/article/details/80155323
shared_ptr的出現在某種程度上解放了c++程式員,c++11标準原生的支援了并發程式設計,在并發程式設計中shared_ptr的線程安全問題如何保證呢?先撇開shared_ptr對象的線程安全性,先看shared_ptr本身的線程安全問題。
我們知道,shared_ptr的底層實作原理是引用計數,關于這個計數是否線程安全呢,如果我們把shared_ptr分别傳遞到不同的線程中,是否會在成引用計數的競争問題。我們來看shared_ptr引用計數的底層實作。shared_ptr繼承了下面的模闆類,用它來管理引用計數。其中有兩個變量一個表示shared_ptr的引用數,另外一個表示weak_ptr的引用數,我們知道weak_ptr不會增加隻能指針的引用數也就是說不持有對象,他的使用必須通過lock方法擷取它指向的shared_ptr才能使用。
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
public:
_Sp_counted_base() noexcept
: _M_use_count(1), _M_weak_count(1) { }
virtual
~_Sp_counted_base() noexcept
{ }
//當_M_use_count為0時調用,是個純虛函數(必須實作),這個函數的作用是釋放指針指向的對象所持有的資源,即*this
virtual void
_M_dispose() noexcept = 0;
// 當_M_weak_count為0時調用,釋放自己本身的資源,即this
// _M_weak_count = _M_weak_count + (_M_use_count!= 0),當_M_weak_count和_M_use_count都為0時釋放this
virtual void
_M_destroy() noexcept
{ delete this; }
virtual void*
_M_get_deleter(const std::type_info&) noexcept = 0;
//增加一個引用
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }
void
_M_add_ref_lock();
bool
_M_add_ref_lock_nothrow();
void
_M_release() noexcept
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
//首先use_count減去1,并對比減操作之前的值,如果減之前是1,說明減後是0,a1沒有任何shared_ptr指針指向它了将銷毀對象
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
_M_dispose();
//如果destory和dispose存在記憶體屏障,保證dispose函數的效果在destory函數的調用該線程的可見性
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
//同時對a1的weak_count減去1,也對比減操作之前的值,如果減之前是1,說明減後是0,a1沒有weak_ptr指向它了,
//應該将管理對象銷毀,于是調用_M_destroy()銷毀了管理對象
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,
-1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
_M_destroy();
}
}
}
void
_M_weak_add_ref() noexcept
{ __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); }
void
_M_weak_release() noexcept
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers)
{
__atomic_thread_fence (__ATOMIC_ACQ_REL);
}
_M_destroy();
}
}
//擷取引用計數
long
_M_get_use_count() const noexcept
{
return __atomic_load_n(&_M_use_count, __ATOMIC_RELAXED);
}
private:
_Sp_counted_base(_Sp_counted_base const&) = delete;
_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;
_Atomic_word _M_use_count;
_Atomic_word _M_weak_count;
};
可能有一些人看過boost中智能指針引用計數的實作,不過好像是通過鎖來實作的。這裡我們可以看到跟boost的是有所不同的,這裡的智能指針的引用計數在手段上使用了atomic原子操作,隻要在shared_ptr在拷貝或指派時增加引用,析構時減少引用就可以了。
首先源自操作是線程安全的,所有智能指針在多線程下引用計數也是安全的,也就是說智能指針在多線程下傳遞使用時引用計數是不會有線程安全問題的,但是這能真正的保證shared_ptr指針的線程安全問題嗎。
雖然通過原子操作解決了引用計數的計數的線程安全問題, 但是智能指針指向的對象的線程安全問題,智能指針沒有做任何的保證。 首先智能指針有兩個變量,一個是指向的對象的指針,還有一個就是我們上面看到的引用計數管理對象, 當智能指針發生拷貝的時候,标準哭的實作是縣拷貝智能指針,再拷貝引用計數對象(拷貝引用計數對象的時候,會使use_count加一),這兩個操作并不是原子的,隐患就出現在這裡,引用一下陳碩老師的例子:位址點選打開連結

這裡陳碩老師說道:“這正是多線程讀寫同一個shared_ptr必須枷鎖的原因”, 為了保證程式的絕對的安全是沒錯的, 但也不是絕對,上面的情景是特殊場景,這種場景也隻是為了說明問題而已,真正開發過程中不一定會用到此場景。其實這個問題的根本還是上面說的智能指針指向的對象的線程安全,shared_ptr沒有做任何保證,上面的情景就打破了這一準則,在指派的過程中,改變了shard_ptr指向的對象的内容,甚至不隻是修改了對象這麼簡單,上面的情景直接把智能指針指向的對象給換了。這中情況不用想肯定會出問題。如果你能保證不會有多個線程同時修改或替換指針指向的對象,不用加鎖是完全沒有問題的,或者說指針指向的對象本身已經是線程安全(包括多線程下的讀寫安全和構造析構安全)。總之一句話智能指針指向的對象的線程安全,标準庫是沒有保證的。