shared_ptr的線程安全問題
首先看看下面的圖,這個圖描述的就是對象、資源、引用計數之間的關係。
shared_ptr的線程安全問題需要從兩個角度來分析:
(1)從引用計數的角度來看:
雖然引用計數存在於每一個shared_ptr
對象中,但是實際上它是要跟隨對象所管理的資源。引用計數會隨着指向這塊資源的shared_ptr
對象的增加而增加。因此引用計數是要指向同一塊資源的所有的對象共享的,所以實際上引用計數在shared_ptr
底層中是以指針的形式實現的,所有的對象通過指針訪問同一塊空間,從而實現共享。
那麼也就是說,引用計數是一個臨界資源,所以在多線程中,我們必須要保證臨界資源訪問的安全性,因此在shared_ptr
底層中在對引用計數進行訪問之前,首先對其加鎖,當訪問完畢之後,在對其進行解鎖。
所以shared_ptr的引用計數是線程安全的。
(2)從被shared_ptr對象所管理的資源來看:
shared_ptr
對象所管理的資源存放在堆上,它可以由多個shared_ptr
所訪問,所以這也是一個臨界資源。因此當多個線程訪問它時,會出現線程安全的問題。
首先shared_ptr
對象有兩個變量,一個是指向的對象的指針,還有一個就是我們上面看到的引用計數, 當shared_ptr
發生拷貝的時候,是先拷貝智能指針,然後再拷貝引用計數,也就是說,shared_ptr的拷貝並不是一個原子操作。而問題就出現在這裏。
用一個簡單的例子來說明:
假如有下面三個同類型的shared_ptr:
shared_ptr<foo> p1; //線程A的局部變量
shared_ptr<foo> p2(new foo); //線程A和線程B所共享
shared_ptr<foo> p3(new foo); //線程B的局部變量
(1)一開始他們之間的關係可以用下圖來表示:
(2)然後線程A先執行語句:p1=p2
,在執行這條語句時,先改變ptr的指向,然後才修改引用計數。因爲現在是多線程,所以很可能出現這樣的情況:在線程A執行完步驟一時,還沒來得及執行步驟二,就輪到線程B來執行。如下圖所示:
(3)現在線程B開始執行p2=p3
,並且沒有被打斷,也就是說步驟一二都完成。
先是步驟一
然後步驟二:
注意此時因爲第一個資源的引用計數已經爲0,所以會銷燬該資源,也就是說,步驟二執行完之後,p1的ptr是一個懸空指針
(4)接下來輪到線程A執行,但是此時線程A是從上一次執行到的地方開始執行,也就是說,線程A會從步驟二開始執行。
所以多個shared_ptr
對象對其所管理的資源的訪問不是線程安全的。如果不使用鎖這會造成線程安全問題。
(5)所以當我們多個線程訪問同一個shared_ptr
時,應該要進行加鎖操作。