C++多線程環境下里如何精確獲取shared_ptr的引用計數?

shared_ptr::use_count在多線程中不精確,shared_ptr::unique在C++17里已經廢棄。

Linux多線程服務端編程使用muduo C++網路庫(書籍)第二章使用shared_ptr替換讀寫鎖的做法是否有缺陷?

http://en.cppreference.com/w/cpp/memory/shared_ptr/use_count

std::shared_ptr::unique - cppreference.com


為什麼想取得引用計數呢?引用計數是多線程共享的,即使你拿到了,等你判斷的時候又被改了怎麼辦?

shared_ptr是為了保證對象會被析構,防止memory leak的,所以它其實並不需要精確計數,只需要在引用計數為0的情況下保證析構對象就行。在多核情況下你要精確計數就必須得用atomic操作,而atomic是很貴的。所以一個很自然的優化就是在shared_ptr上放一個全局計數,然後在本線程做個buffer count,本線程計數從0到1的時候和從1到0的時候用atomic去操作全局計數,而其它時候只需要操作本線程計數。

這樣就帶來一個問題,那就是你沒辦法精確統計目前到底有多少個計數,因為每個線程都會有buffer count。所以shared_ptr::use_count才只有在返回0和1的情況下是確定的(至少在你做那個atomic操作的時候是確定的),其它時候都是不確定的。


我的書上的做法在 C++17 里也是正確的,因為代碼用 Mutex 保護了 unique(),不存在 race condition。


原子計數其實是個可寫而不可讀的量。

對他的唯一有效訪問就是原子增減操作後的返回值,這很類似於C++輸入迭代器的概念。


自己加互斥量


本來是std::shared_ptr的一個BUG, 那些C++委員會的人不去修復BUG, 直接將unique()函數給廢棄掉了,很好,這樣就不用修改代碼了,也保全了顏面.

如何精確獲取shared_ptr的引用計數? 

這個問題很簡單,要麼修改std::shared_ptr的代碼,要麼自己重新創建一套shared_ptr.

為什麼必須要廢棄掉unique()這個函數, 因為只有1這個值是不可靠的,其餘的值都是可靠的.

在一些情況下,其實應該檢測到unique()==true, 但是卻檢測到==false,

出現此問題的原因在boost::shared_ptr庫代碼 /usr/include/boost/smart_ptr/detail/shared_count.hpp 中類似下面代碼的地方

impl_type * pi = std::allocator_traits&::allocate( a2, 1 );

意思是將 shared_ptr 指針的引用計數器設置為初值1,這裡採用了普通的賦值. 此代碼執行後,由於現代CPU的亂序執行,以及推遲寫入內存,不能夠保證這個值立即被傳送到所有CPU,或者說需要過足夠長的時間所有CPU才可以看到這個1值,

如果某個shared_ptr1剛被創建完,然後std::move的方式傳送給另外一個線程B,線程B立即檢測shared_ptr1.unique(), 那麼就可能會得到一個隨機值(取決於舊值).

此BUG的修復辦法:前面提到的代碼修改為

impl_type * pi = std::allocator_traits&::allocate( a2, 0 );

pi-&>add_ref_copy();

這樣add_ref_copy()內部會實現一條CAS指令實現置1,CAS指令會立即廣播到所有的CPU,所有CPU都可以在此CAS後看到這個1值.

我廠重新實現了shared_ptr,修復了shared_ptr內部的BUG, 例如上面這個,實現了完全線程安全,性能比原始shared_ptr提高一倍,實現了第二代智能指針(即完全線程安全的 C++20計劃引入std::atomic_shared_ptr 來實現完全線程安全),並且在此基礎上實現了第三代智能指針,內置類似java gc的功能,減少了析構的開銷,使得智能指針的綜合性能超越了裸指針的綜合性能.


不精確的原因文檔本身已經說得很清楚了,那是假設不同線程都在操作同一個shared_ptr的情況。這種情況要是能夠精確獲得才是奇怪了,真要精確獲得就再加一把鎖。


推薦閱讀:

如何評價Andrei Alexandrescu提出的c++ policy-based design?
為什麼libstdc++的allocator要實現兩個operator==?
為什麼在 C++ 中不提倡 C 風格的強制類型轉換?
你讀過的最好的 C++ 開源代碼是什麼?
C++17 基本完成,對於新特性大家怎麼看?

TAG:C | 並發 | C17 | Linux多線程服務端編程:使用muduoC網路庫書籍 |