智能指針是否線程安全


1.9 再論shared_ptr 的線程安全

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

一個shared_ptr 對象實體可被多個線程同時讀取;

兩個shared_ptr 對象實體可以被兩個線程同時寫入,“析構”算寫操作;

如果要從多個線程讀寫同一個shared_ptr 對象,那么需要加鎖。

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

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

  1. MutexLock mutex; // No need for ReaderWriterLock  
  2. shared_ptr<Foo> globalPtr;  
  3. // 我們的任務是把globalPtr 安全地傳給doit()  
  4. void doit(const shared_ptr<Foo>& pFoo); 

globalPtr 能被多個線程看到,那么它的讀寫需要加鎖。注意我們不必用讀寫鎖,而只用最簡單的互斥鎖,這是為了性能考慮。因為臨界區非常小,用互斥鎖也不會阻塞並發讀。

為了拷貝globalPtr,需要在讀取它的時候加鎖,即:

  1. void read()  
  2. {  
  3. shared_ptr<Foo> localPtr;  
  4. {  
  5. MutexLockGuard lock(mutex);  
  6. localPtr = globalPtr; // read globalPtr  
  7. }  
  8. // use localPtr since here,讀寫localPtr 也無須加鎖  
  9. doit(localPtr);  

 

寫入的時候也要加鎖:

  1. void write()  
  2. {  
  3. shared_ptr<Foo> newPtr(new Foo); // 注意,對象的創建在臨界區之外  
  4. {  
  5. MutexLockGuard lock(mutex);  
  6. globalPtr = newPtr; // write to globalPtr  
  7. }  
  8. // use newPtr since here,讀寫newPtr 無須加鎖  
  9. doit(newPtr);  

 

注意到上面的read() 和write() 在臨界區之外都沒有再訪問globalPtr,而是用了一個指向同一Foo 對象的棧上shared_ptr local copy。下面會談到,只要有這樣的local copy 存在,shared_ptr 作為函數參數傳遞時不必復制,用reference to const 作為參數類型即可。另外注意到上面的new Foo 是在臨界區之外執行的,這種寫法通常比在臨界區內寫globalPtr.reset(new Foo) 要好,因為縮短了臨界區長度。如果要銷毀對象,我們固然可以在臨界區內執行globalPtr.reset(),但是這樣往往會讓對象析構發生在臨界區以內,增加了臨界區的長度。一種改進辦法是像上面一樣定義一個localPtr,用它在臨界區內與globalPtr 交換(swap()),這樣能保證把對象的銷毀推遲到臨界區之外。練習:在write() 函數中,globalPtr = newPtr; 這一句有可能會在臨界區內銷毀原來globalPtr 指向的Foo 對象,設法將銷毀行為移出臨界區。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM