練習 自己動手實現乙個輕量級的訊號量(一)

2022-02-12 08:10:09 字數 4039 閱讀 2797

訊號量歷史悠久,折磨死了一代又一代的計算機專業學生,但是不得不承認其在multi-thread環境下的巨大作用。最經典的案例莫過於管理乙個環狀緩衝區。.net 中的semaphore物件等同於win32中的semaphore。屬於核心級物件,因此使用它的代價就比較大了。並且semaphore物件每次僅僅能夠等待乙個count,這有的時候讓事情變得有些煩,例如你可能不得不將環狀緩衝區分割為乙個個的chunk(實際上這是乙個好方法,因為我們應該對於cache進行優化)。qt中的訊號量可以一次獲得多個count,感覺很方便。綜上,我們希望自己動手實現乙個輕量級的,支援一次獲得多個資源的訊號量。

首先看看訊號量重要維護哪些資訊。第一,當前還有多少剩餘的資源(_currentresources);第二,系統初始化的時候有多少資源(_initresources);第三,當前有多少個執行緒被阻塞(_waitingthreadcount):

1public

class

semaphoreextended

2我們知道,如果訊號量持有的資源數目沒完沒了的減少,那麼降到0就會阻塞,但是沒完沒了的增加呢?厄~你說會溢位,所以,我們還需要進行乙個上限的檢查。於是我們新增了乙個上限值,這個上限值應該由使用者指定,並且一旦指定就不能更改了。於是我們的**中新增了乙個新的field。

1public

class

semaphoreextended

2好的,你發現了,我們的需要管理的fields不止乙個,那麼~~對了,為了保證某些操作的一致性,我們肯定需要一把鎖。

1public

class

semaphoreextended

2好了,下面我們可以開始我們的工作了。下一步我們幹什麼呢?厄~咱們解決掉關鍵問題之後剩下的工作肯定輕鬆愉快,因此,我們先考慮wait和release吧。

首先解決release的問題,想想release要做哪些事情。

(1)看看當前的資源數目加上釋放的數目會不會超過上限

(2)如果不超過上限就增加我們的資源數目

(3)如果發現有的執行緒在等待資源則啟用那些執行緒,讓他們看看現有的資源是不是夠他們使用了

(4)將之前的資源數目返回給使用者。還有關鍵的一點,這些操作必須保證是atomic的,於是這些操作都需要lock!

寫**了~~~

1public

intrelease(

intreleaserescount)214

//很好,現在我們在做第(2)步

15this

._currentresources 

+=releaserescount;

16//

我們現在可以做第(3)步了

17//

為了效能考慮,我們應當分兩種情況進行討論。

18//

當然了,如果沒有執行緒等待就不管了~

19//

什麼?萬一你在判斷的時候等待的執行緒增加了怎麼辦?有鎖呢,不可能~

20if

(this

._waitingthreadcount ==1

)2124elseif(

this

._waitingthreadcount 

>1)

2528

//這就是第(4)步

29return

(this

._currentresources 

-releaserescount);30}

31}以上的**正確麼?答案是錯誤的!錯誤在了第(3)步,最終導致第4步結果可能是錯誤的。

問題出在了pulse上。如果你發現看msdn上的解釋有些頭暈,那麼我們就用偽**解釋一下。

monitor.pulse(...)

等價於if(_globallock.owner == thread.currentthread)

else

internalpulse(...);

monitor.enter(_globallock);

問題已經出來了,實際上我們喚醒執行緒時釋放了鎖,喚醒之後只有重新獲得鎖之後才能繼續,而這個時候訊號量擁有的資源數目很可能已經改變了!我們說,release返回給使用者的資料是呼叫release之前訊號量所保有的資源數目,那麼,我們就應當提前儲存這個值。修改一下:

1public

intrelease(

intreleaserescount)214

//很好,現在我們在做第(2)步

15int

old 

=this

._currentresources;

16this

._currentresources 

+=releaserescount;

17//

我們現在可以做第(3)步了

18//

為了效能考慮,我們應當分兩種情況進行討論。

19//

當然了,如果沒有執行緒等待就不管了~

20//

什麼?萬一你在判斷的時候等待的執行緒增加了怎麼辦?有鎖呢,不可能~

21if

(this

._waitingthreadcount ==1

)2225elseif(

this

._waitingthreadcount 

>1)

2629

//這就是第(4)步

30return

old;31}

32}好,目前我們輕鬆的解決了release問題。但是wait就不是那麼好辦的了。想這個花掉很多腦細胞。首先乙個問題,wait可能阻塞,所以必須支援超時操作。其次就是必須考慮執行緒喚醒之後的工作。

我們還是先理一下思路。wait試圖減少資源數目。如果失敗就需要不停的等待,直到他被別人喚醒。首先用文字書寫一下:

(1)試圖獲得鎖!這是必須的因為沒有鎖的話我們根本沒有辦法保證操作的一致性。這個時候我們不能夠使用lock因為我們必須處理超時的問題。

(2)好樣的,鎖在我們手裡了,現在我們要試圖減少資源數目:

while(當前的資源數還不夠我們減少的)

釋放掉鎖並讓執行緒處於等待狀態。如果超時就返回;

// 好了,執行到這一句說明我們已經持有鎖了,有人把我們喚醒了

// 但是喚醒了不代表我們就有我們需要的資源了所以迴圈回去檢查

}// 能執行到這一句說明:(a)我們擁有鎖,(b)我們有足夠的資源

減少資源數目;

(3)釋放鎖。

我們來寫**!

1public

bool

wait(

intmillisecondstimeout, 

intwaitresnumber)214

//現在我們來做第(1)步

15if(!

system.threading.monitor.tryenter(

this

._globallock))

1623}24

//現在我們已經獲得鎖了,我們要增加阻塞的執行緒數目,以便將來有人能夠喚醒我們

25++

this

._waitingthreadcount;

26try

2739}40

//如果沒有超時我們就再等一下

41if(!

system.threading.monitor.wait(

this

._globallock, timeoutnum))

4245

//好的我們被喚醒了,趕快回去檢查檢查有沒有足夠的資源了46}

47//

很好,我們現在有足夠的資源了,

48//

並且沒有什麼人能夠再更改資源數目了,因為鎖在我們手裡!

49this

._currentresources 

-=waitresnumber;50}

51finally

5257

return

true;58

}好的!我們基本上實現了乙個訊號量的精華部分了。但是這是不夠的。尤其是在wait的過程中,我們直接就使用了monitor,實際上,對於多核心處理器來說,一開始進行短暫的spin是更好的選擇!我們的**還有優化的空間!下一回,我們就在wait中新增spin優化,並最終實現乙個完整的訊號量。

乙個輕量級AOP的實現(開源)

事先宣告,本專案參考aop in c 和園內大神張逸的文章,思路神馬的都不是自己的!為了讓專案的 看起來更乾淨,需要乙個aop!於是就實現了乙個非常簡單的,非常輕量級,有多輕量級呢?實現的aop叫做earthworm 蚯蚓,為什麼叫這個?因為它小,它會疏通!專案的本意也是這樣,所以就叫這個!命名空間...

自己設計乙個的輕量級的RPC框架 服務自動限流

之前被各種事情耽擱了我的rpc框架,最近抽了一點時間繼續寫。上篇寫了服務的手動降級,這篇主要寫關於服務自動限流。通常情況下無論是客戶端還是服務端都需要對於突發事件有相應的處理。服務端 服務的降級和限流 面對突發的大流量,服務端的自我保護措施,例如直接停止服務或者1分鐘僅限10次呼叫 客戶端 服務的容...

自己動手程式設計實現乙個shell

這是本部落格的第乙個文章 主要介紹如何用 c 語言基於linux系統來實現乙個簡單shell,diy 乙個shell。通過自己程式設計實現乙個linux下的 shell,可以使得個人對程序的概念 程序的通訊和作業系統的執行的理解更加的深刻。還會大大增加個人學習的成就感,提供學習興趣。這乙個文章 被命...