多執行緒讀寫優化(雙buff記憶體交換代替有鎖設計)

許佳佳233發表於2018-07-22

例子(場景)

目前有執行緒ThreadA和ThreadB,一個佇列Queue。ThreadA會對Queue進行入隊操作,而ThreadB會對Queue進行出隊操作。如下圖:
這裡寫圖片描述
一般情況下,我們都會直接給Queue上鎖,這樣就能保證多執行緒同時對Queue進行操作時不會有問題。
直接加上鎖可以很容易就解決這個問題,但是也會帶來其他的問題:入隊操作一般幾乎不耗時,而出隊操作往往帶有其他一系列邏輯操作,所以會比較耗時。因此ThreadA本來做完一系列入隊操作可能只要3ms,但是由於等待ThreadB的鎖的釋放,可能多等待了200ms。如下圖:
這裡寫圖片描述

在這種情況下,如果ThreadA在入隊操作還有其他邏輯,那麼後面的邏輯會被延後200ms執行,這是完全沒有必要的,因此便可以通過以下方式優化。

優化

直接使用兩個Queue物件,一個只給ThreadA用來入隊,一個只給ThreadB用來出隊,這樣入隊和出隊操作就可以分離,不用去爭搶鎖。
達到一定觸發條件的時候兩個Queue的記憶體就進行交換,原來入隊的Queue變為出隊的Queue,出隊的Queue變成入隊的Queue。這個觸發條件可以由ThreadA來控制,在ThreadA認為不需要繼續入隊並且ThreadB的佇列為空的時候,兩個Queue可以進行交換。如下圖:
這裡寫圖片描述
這樣之後,在時間上的表現就變為下圖。對於ThreadA來講,一次將幾乎不耗時的入隊操作做完,後面如果有其他邏輯可以不會被耽誤。而對ThreadB來講,本來執行的操作可能就比較耗時,等待ThreadA的入隊操作時間也非常短,所以影響不大。
這裡寫圖片描述

補充

可能讀者不太理解為什麼出隊操作會那麼耗時。因為上面是假設的出隊與後續邏輯操作連在一起的情況。
那麼是否意味著出隊之後直接釋放鎖,這種情況就不適用了呢?不是的。
入隊操作需要佔用一次鎖和釋放一次鎖,出隊操作同樣是的。如果每出隊一次就需要佔用和釋放一次鎖,那麼如果有100個就需要佔用和釋放鎖一百次,這是在數量較多的情況下是非常消耗資源的了。
因此把鎖給去掉在這種情況下也是有相當的優化價值。

相關文章