標籤:

多線程引用計數如何釋放?

我是指計數自身佔用的資源如何釋放(內存、相關的鎖等)

以std::shared_ptr而言,就是指ref count所在的那塊內存

可以隨意使用鎖,interlock等工具,唯一要求就是這些相關資源也要釋放,不能無法釋放(即最終相關資源都已釋放)

我試了很多方法,但是都會遇到一個問題,即在Release的時候,另一個線程試圖獲取引用,然後訪問到已釋放資源(要麼是refcount++時,refcount的內存已釋放,要麼是嘗試取鎖,鎖已經釋放)。

以下是把關鍵步驟抽出來,用c++演示問題

struct RefObj {
int refcount = 1;
}
RefObj* share = new RefObj();
void thread1(){
share-&>refcount++; //1
RefObj* ref1 = share;
//use ref1;
}
void thread2(){
int current = --(share-&>refcount);
if(current == 0){
delete share; //2
}
}

指定執行順序為:

2 =&> 1

------------------------------------------------------------------------------------------------------------------------

我總結一下自己的看法把,一個資源自己釋放自己有困難,所以讓別的資源來管理它,這個思路把問題轉化為了,如何管理所謂的別的資源,如果別的資源按同樣的思路做,這就成了一個無限循環。

這樣回答問題太累了,每說明一個老結構沒有問題,都是把問題轉移到了其他地方,然後我又得說新地方有問題。

我就講講我自己想到的思路把,我覺得要徹底解決這個問題,還是回到了怎樣確保 在其他所有相關線程都沒有用這個資源 這個問題上。

比如說GC解決了這個問題,

除了GC我還想到的辦法就是在所有相關線程,用我們指定的代碼佔用之,這確保了相關線程肯定沒有使用資源,再進行釋放。這種方法適用於線程上有消息隊列,可以派發方法到指定線程執行的情況。(這種方法的粗暴版,就是殺掉這些線程或迫使其拋異常跳出)

有其他思路歡迎補充


對,我看別人也提到了,在你這裡我最感到困惑的一件事情是引用計數會在減到0之後(或者同時)又增加回到0以上,這即使是在單線程當中也是有問題的,也會先釋放掉資源。任何時候如果你要在Release自己的引用之前將引用潛在性地移交給別人,你都應該先進性一次引用計數遞增的操作。如果說你需要跨線程進行非同步的轉移,那麼你應該在轉出之前(比如放進隊列之前)就為接受線程進行一次AddRef,這樣才能保證自己的Release是安全的。否則就算你是單線程,你也有可能把還放在隊列里的資源給Release掉了。哪怕資源是在隊列里放著,你也得給它提供一個額外的引用計數才對。

如果說你的另外一個線程在沒有自己的引用計數的情況下去讀取了一個資源(哪怕他立即想要獲取一個引用計數),這說明你的線程借用了別人的一個引用,那出借的一方自然就需要保證借出去的引用在還回來之前不能Release,這中間需要加鎖,但我們本來用引用計數就是為了共享,那麼再引入一層鎖互斥就很奇怪了。

正常來說,如果你要在兩個線程當中共享一個帶引用計數的對象,你需要有一個傳遞的機制,舉個例子來說,你將對象引用放在一個共享的位置上,同時AddRef(這個Ref是你這個共享位置的引用計數),然後通知另一個線程;另一個線程取走這個Ref,把這個引用計數轉移到自己手上。注意在通知另一個線程之前就要AddRef,而不是在另一個線程取的時候才Add,這是因為你放在共享位置的那個引用也需要一個自己的引用計數,來保證另一個線程取走之前,這個對象的引用計數不會減到0。


https://github.com/chenshuo/recipes/blob/master/basic/counted_ptr.h

https://chenshuo-public.s3.amazonaws.com/pdf/chap1.pdf


多線程不加鎖訪問共享資源的話要用原子操作,要注意內存屏障和可見性,防止編譯器亂優化和其他線程讀到CPU緩存。

另外shared_ptr的維護生命周期的數據塊里有弱引用計數,可以用來維護這個數據塊什麼時候釋放

話說回來忘記哪裡看到得了,即便是shared_ptr,新增和、刪除和引用計數增減都是線程安全的,但是如果要變更指向的對象也不是安全的,因為總歸裡面兩個指針有先後順序,如果只改了一個指針的情況下被其他線程讀了,還是有可能GG


首先,引用計數所在的內存的任何訪問操作必須是原子的,當然你可以加鎖或者用相關 Atomic 函數。

引用使用權轉移的時候不能存在引用計數已經為零的情況,既然你已經讓 thread 2 先運行了,所有內存都會釋放,thread 1 必然會出現 Bad Access 的情況,Objective-C 里的 autorelease 可以讓引用計數變為零且在下一個 runloop 之前不會被釋放,但是你的這個程序在引用計數為零時會立即釋放相應內存,這就是問題所在。


你這套想法換成CPython就是GIL啊旁友!


出錯的原因在於你將一種單線程的解決方式,放在多線程的問題上


推薦閱讀:

C++中如何將函數調用轉發至另一個線程?
互斥鎖,同步鎖,臨界區,互斥量,信號量,自旋鎖之間聯繫是什麼?
C# 如何在調用控制項時做到 Thread-safe(線程安全)?
多線程是否有意義?
boost 是否像 Linux 一樣提供讀寫自旋鎖機制?

TAG:多線程 |