一:
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.
多線程環境下,調用不同shared_ptr實例的成員函數是不需要額外的同步手段的,即使這些shared_ptr擁有的是同樣的對象。但是如果多線程訪問(有寫操作)同一個shared_ptr,則需要同步,否則就會有race condition 發生。也可以使用 shared_ptr overloads of atomic functions 來防止race condition的發生。
To satisfy thread safety requirements, the reference counters are typically incremented using an equivalent of std::atomic::fetch_add with std::memory_order_relaxed (decrementing requires stronger ordering to safely destroy the control block).
shared_ptr的引用計數本身是安全且無鎖的。
http://en.cppreference.com/w/cpp/memory/shared_ptr
二:
It is only the control block itself which is thread-safe.
I put that on its own line for emphasis. The contents of the shared_ptr are not thread-safe, nor is writing to the same shared_ptr instance. Here's something to demonstrate what I mean:
// In main()
shared_ptr<myClass> global_instance = make_shared<myClass>();
// (launch all other threads AFTER global_instance is fully constructed)
//In thread 1
shared_ptr<myClass> local_instance = global_instance;
This is fine, in fact you can do this in all threads as much as you want. And then when local_instance is destructed (by going out of scope), it is also thread-safe. Somebody can be accessing global_instance and it won't make a difference. The snippet you pulled from msdn basically means "access to the control block is thread-safe" so other shared_ptr<> instances can be created and destroyed on different threads as much as necessary.
//In thread 1
local_instance = make_shared<myClass>();
This is fine. It will affect the global_instance object, but only indirectly. The control block it points to will be decremented, but done in a thread-safe way. local_instance will no longer point to the same object (or control block) as global_instance does.
//In thread 2
global_instance = make_shared<myClass>();
This is almost certainly not fine if global_instance is accessed from any other threads (which you say you're doing). It needs a lock if you're doing this because you're writing to wherever global_instance lives, not just reading from it. So writing to an object from multiple threads is bad unless it's you have guarded it through a lock. So you can read from global_instance the object by assigning new shared_ptr<> objects from it but you can't write to it.
結論:多個線程同時讀同一個shared_ptr對象是線程安全的,但是如果是多個線程對同一個shared_ptr對象進行讀和寫,則需要加鎖。
// In thread 3
*global_instance = 3;
int a = *global_instance;
// In thread 4
*global_instance = 7;
The value of a is undefined. It might be 7, or it might be 3, or it might be anything else as well. The thread-safety of the shared_ptr<> instances only applies to managing shared_ptr<> instances which were initialized from each other, not what they're pointing to.
多線程讀寫shared_ptr所指向的同一個對象,不管是相同的shared_ptr對象,還是不同的shared_ptr對象,也需要加鎖保護。例子如下:
shared_ptr<long> global_instance = make_shared<long>(0);
std::mutex g_i_mutex;
void thread_fcn()
{
//std::lock_guard<std::mutex> lock(g_i_mutex);
//shared_ptr<long> local = global_instance;
for(int i = 0; i < 100000000; i++)
{
*global_instance = *global_instance + 1;
//*local = *local + 1;
}
}
int main(int argc, char** argv)
{
thread thread1(thread_fcn);
thread thread2(thread_fcn);
thread1.join();
thread2.join();
cout << "*global_instance is " << *global_instance << endl;
return 0;
}
在線程函數thread_fcn的for循環中,2個線程同時對*global_instance進行加1的操作。這就是典型的非線程安全的場景,最后的結果是未定的,運行結果如下:
*global_instance is 197240539
如果使用的是每個線程的局部shared_ptr對象local,因為這些local指向相同的對象,因此結果也是未定的,運行結果如下:
*global_instance is 160285803
因此,這種情況下必須加鎖,將thread_fcn中的第一行代碼的注釋去掉之后,不管是使用global_instance,還是使用local,得到的結果都是:
*global_instance is 200000000
https://stackoverflow.com/questions/14482830/stdshared-ptr-thread-safety
三:為什么多線程讀寫 shared_ptr 要加鎖?
以下內容,摘自陳碩的 http://blog.csdn.net/solstice/article/details/8547547
shared_ptr的引用計數本身是安全且無鎖的,但對象的讀寫則不是,因為 shared_ptr 有兩個數據成員(指向被管理對象的指針,和指向控制塊的指針),讀寫操作不能原子化。根據文檔(http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr 的線程安全級別和內建類型、標准庫容器、std::string 一樣,即:
• 一個 shared_ptr 對象實體可被多個線程同時讀取(文檔例1);
• 兩個 shared_ptr 對象實體可以被兩個線程同時寫入(例2),“析構”算寫操作;
• 如果要從多個線程讀寫同一個 shared_ptr 對象,那么需要加鎖(例3~5)。
請注意,以上是 shared_ptr 對象本身的線程安全級別,不是它管理的對象的線程安全級別。
本文具體分析一下為什么“因為 shared_ptr 有兩個數據成員,讀寫操作不能原子化”使得多線程讀寫同一個 shared_ptr 對象需要加鎖。這個在我看來顯而易見的結論似乎也有人抱有疑問,那將導致災難性的后果,值得我寫這篇文章。本文以 boost::shared_ptr 為例,與 std::shared_ptr 可能略有區別。
1:shared_ptr 的數據結構
shared_ptr 是引用計數型(reference counting)智能指針,幾乎所有的實現都采用在堆(heap)上放個計數值(count)的辦法(除此之外理論上還有用循環鏈表的辦法,不過沒有實例)。具體來說,shared_ptr<Foo> 包含兩個成員,一個是指向 Foo 的指針 ptr,另一個是 ref_count 指針(其類型不一定是原始指針,有可能是 class 類型,但不影響這里的討論),指向堆上的 ref_count 對象。ref_count 對象有多個成員,具體的數據結構如圖 1 所示,其中 deleter 和 allocator 是可選的。

圖 1:shared_ptr 的數據結構。
為了簡化並突出重點,后文只畫出 use_count 的值:
以上是 shared_ptr<Foo> x(new Foo); 對應的內存數據結構。
如果再執行 shared_ptr<Foo> y = x; 那么對應的數據結構如下。

但是 y=x 涉及兩個成員的復制,這兩步拷貝不會同時(原子)發生。中間步驟 1,復制 ptr 指針:

中間步驟 2,復制 ref_count 指針,導致引用計數加 1:

步驟1和步驟2的先后順序跟實現相關(因此步驟 2 里沒有畫出 y.ptr 的指向),我見過的都是先1后2。
既然 y=x 有兩個步驟,如果沒有 mutex 保護,那么在多線程里就有 race condition。
2:多線程無保護讀寫 shared_ptr 可能出現的 race condition
考慮一個簡單的場景,有 3 個 shared_ptr<Foo> 對象 x、g、n:
shared_ptr<Foo> g(new Foo); // 線程之間共享的 shared_ptr
shared_ptr<Foo> x; // 線程 A 的局部變量
shared_ptr<Foo> n(new Foo); // 線程 B 的局部變量
一開始,各安其事:

線程 A 執行 x = g; (即 read g),以下完成了步驟 1,還沒來及執行步驟 2。這時切換到了 B 線程。

同時編程 B 執行 g = n; (即 write g),兩個步驟一起完成了。先是步驟 1:

再是步驟 2:

這時 Foo1 對象已經銷毀,x.ptr 成了空懸指針!
最后回到線程 A,完成步驟 2:

多線程無保護地讀寫 g,造成了“x 是空懸指針”的后果。這正是多線程讀寫同一個 shared_ptr 必須加鎖的原因。
當然,race condition 遠不止這一種,其他線程交織(interweaving)有可能會造成其他錯誤。
雜項
shared_ptr 作為 unordered_map 的 key
如果把 boost::shared_ptr 放到 unordered_set 中,或者用於 unordered_map 的 key,那么要小心 hash table 退化為鏈表。
http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314
直到 Boost 1.47.0 發布之前,unordered_set<std::shared_ptr<T> > 雖然可以編譯通過,但是其 hash_value 是 shared_ptr 隱式轉換為 bool 的結果。也就是說,如果不自定義hash函數,那么 unordered_{set/map} 會退化為鏈表。https://svn.boost.org/trac/boost/ticket/5216
Boost 1.51 在 boost/functional/hash/extensions.hpp 中增加了有關重載,現在只要包含這個頭文件就能安全高效地使用 unordered_set<std::shared_ptr> 了。
這也是 muduo 的 examples/idleconnection 示例要自己定義 hash_value(const boost::shared_ptr<T>& x) 函數的原因(書第 7.10.2 節,p.255)。因為 Debian 6 Squeeze、Ubuntu 10.04 LTS 里的 boost 版本都有這個 bug。
為什么要盡量使用 make_shared()?
為了節省一次內存分配,原來 shared_ptr<Foo> x(new Foo); 需要為 Foo 和 ref_count 各分配一次內存,現在用 make_shared() 的話,可以一次分配一塊足夠大的內存,供 Foo 和 ref_count 對象容身。數據結構是:

不過 Foo 的構造函數參數要傳給 make_shared(),后者再傳給 Foo::Foo(),這只有在 C++11 里通過 perfect forwarding 才能完美解決。
(.完.)
