如何修改shared_ptr智能指針,讓他支持多線程?


反對匿名用戶的答案,(boost或C++11的)shared_ptr不是線程安全的,讀安全,寫不安全

最簡單的方法就是給shared_ptr用鎖保護,因為如果想要修改shared_ptr內部的實現來支持多線程,寫操作時會涉及到多個地址的更改,用簡單的單地址的CAS也是做不到的。所以,不想使用鎖的話,最好對shared_ptr只讀不寫

===========================

放嘲諷:下面答案和評論的知友們,你們真的明白什麼是線程安全和線程不安全嘛 =。=


https://isocpp.org/files/papers/N4162.pdf

Shared_ptr atomic access, revision 1

更新一下,std::atomic&&> 已經進入標準。Revising atomic_shared_ptr for C++20


shared_ptr是值語義,多個線程對shared_ptr賦值和對一個結構體賦值一樣的。單就shared_ptr來說只要保證賦值時不衝突即可。另外shared_ptr指向的東西是否線程安全另說。


傳送門:http://www.cppblog.com/Solstice/archive/2013/01/28/197597.html


線程安全個人理解:
存在同一存儲空間的讀,是線程安全的。同一存儲空間只要涉及到了寫(邊讀邊寫,邊寫邊寫),就不安全了。

有個想法是寫一個wrapped_shared_ptr template.把寫操作加上鎖和讀操作區分開來。


我們實現了完全線程安全的 shared_ptr

例如:

// shared_ptr&::shared_ptr( const shared_ptr& shared_ptr_in )

// 若發現 shared_ptr_in 是最後一個指向某地址塊的共享指針,就需要解決此時的多線程操作的安全性問題

// 解決辦法:

// 實質上 實現了一個 樂觀鎖來檢測衝突, 發現衝突後自動重新來過, 並且正在釋放的 shared_ptr也需要具備檢測到衝突後延遲 X 秒之後再釋放. 否則, 可能其他線程操作到已釋放的內存。我們實現了類似java的延遲垃圾回收機制。

我們重寫了整個shared_ptr庫的代碼,並且糾正了至少一處std::shared_ptr庫底層代碼庫的BUG。

我們壓縮了shared_ptr的內部兩個計數器shared_count/werk_cout,分別放到一個64bit整數的32bit低位和高位32bit, 更進一步解決了釋放衝突的問題,性能上也比std庫的實現快。


用硬體的DCAS指令可以使std::shared_ptr實現無鎖線程安全。最新Linux和Windows都是支持DCAS硬體指令的,所以最新C++編譯器中,std::atomic_is_lock_free&&>(...)在這些平台上通常為true,但在早期版本平台(例如Windows 7)上由於操作系統不支持則通常為false。


這種問題應該去stackoverflow去問的。
iso主席herb sutter在他的"effective concurrency"中的回答:GotW #95 Solution: Thread Safety and Synchronization


自己擼一個線程安全的


雖然借用shared_ptr來實現線程安全的對象釋放,但是shared_ptr本身不是100%線程安全的。它的引用記數本身是安全且無鎖的,但對象的讀寫則不是,因為shared_ptr有兩個數據成員,讀寫操作不能原子化。shared_ptr的線程安全級別和內建類型、標準庫容器、std::string一樣。

一個shared_ptr對象實體可被多個線程同時讀取。
兩個shared_ptr對象實體可以被兩個線程同時寫入,「析構」算寫操作。
如果要從多個線程讀寫同一個shared_ptr對象,那麼需要加鎖。

上面是shared_ptr對象本身的線程安全級別,不是它管理的對象的安全級別。

要在多個線程中同時訪問同一個shared_ptr,正確的做法是用mutex保護。

local copy存在,shared_ptr作為函數參數傳遞時不必複製。


shared_ptr本來就是線程安全的,除非你自己傳的是引用

-----------------------------
有人說shared_ptr不是線程安全的,補充一下,這是vs2013的部分實現代碼。
另外我說的除非你自己傳的是引用意思是說,除非你把一個線程里的shared_ptr當引用傳到另一個線程里去了,這樣肯定不是線程安全的。

代碼里_InterlockedCompareExchange明顯是原子操作。若不是為了線程安全,何必多此一舉。

bool _Incref_nz()
{ // increment use count if not zero, return true if successful
for (; ; )
{ // loop until state is known
#if defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE)
_Atomic_integral_t _Count =
static_cast&(_Uses);

if (_Count == 0)
return (false);

if (static_cast&<_Atomic_integral_t&>(_InterlockedCompareExchange(
reinterpret_cast&(_Uses),
_Count + 1, _Count)) == _Count)
return (true);

#else /* defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE) */
_Atomic_integral_t _Count =
_Load_atomic_counter(_Uses);

if (_Count == 0)
return (false);

if (_Compare_increment_atomic_counter(_Uses, _Count))
return (true);
#endif /* defined(_M_IX86) || defined(_M_X64) || defined(_M_CEE_PURE) */
}
}


Ptr 就算做到線程安全了,指向的對象不是線程安全一樣玩兒完。C++ RAII 式包裝在單線程里玩玩就好,擴展到多線程純屬自找混亂。
用一個綁定線程到auto_ptr 最好,確保任何時間只有一個線程可以訪問對象。


推薦閱讀:

開發多線程的程序應該注意哪些問題?

TAG:編程 | C | 智能指針 | 多線程 | BoostC庫 |