實現無鎖的棧與佇列 1

2022-07-06 23:54:14 字數 2232 閱讀 2897

為了實現乙個快速

無鎖的 logging 模組, 這幾天花了不少時間去了解怎樣實現一些無鎖的操作及與之相對應的資料結構。對多執行緒場景下的無鎖操作的研究一直是個熱點,理想中的無鎖操作,它應能

天然地避開有鎖操作的一些缺陷,比如:

1)減少執行緒切換,能夠相對快速高效地讀寫(不使用 mutex, semaphore)

2)避免死鎖的可能,任何操作都應能在有限的等待時間內完成,

這些優點是很有吸引力的,它們從根本上繞開了有鎖操作可能引起的令人頭疼的同步死鎖問題,那麼它會是我們的救世主嗎? 要了解無鎖的資料結構,我們不妨先來回顧一下常規的資料結構是怎麼寫。

1

// 一般的棧。

23 typedef elem int;4

#define max (2048)56

static

elem stack[max];

7static

int top = 0;8

9bool push(const elem&val)

1017

1819

bool pop(elem&val)

20

這樣的棧在單執行緒場合下是常見的,也很簡潔明瞭,但它卻不適用於多執行緒的場合,試想一下,如果兩個執行緒,執行緒 a, 執行緒 b, 同一時間對同乙個棧進行 push 操作,參考上面的**,假設此時 top = 0, 如果執行緒 a 在執行到第13 行時停了下來,切換到執行緒 b 進行 push,執行緒 b 執行完 13 行,但沒有執行 14 行的時候,這時 stack[top] 中已經插入了執行緒 b 要插入的值,但 top 還沒更新,如果這時執行緒 b 不幸又被切換了出去,換到執行緒 a 繼續執行,那麼執行緒 a 又會在同樣乙個位置 top = 0 的地方插入,從而破壞了執行緒b的操作!

我們可以觀察到,上面的**在多執行緒下之所以不安全,是因為 stack 被多個執行緒同時修改,但各個執行緒又沒有對關鍵的變數在訪問順序上作保護。對此,我們可以引入一些同步的機制來修改它,使得它能在多執行緒的場合裡是操作安全的。   

1

//帶鎖的棧。

23 typedef elem int;4

#define max (2048)56

static

elem stack[max];

7static

int top = 0;8

9static

mutex mutex;

1011

bool

push(elem val)

1224

2526

bool pop(elem&val)

27

上面的**就是我們常說的有鎖操作了,mutex 保證了各個執行緒對公共變數的訪問是安全的,各個執行緒在同時對 stack 進行操作時,需要先搶占 mutex,搶到就可以對 stack 進行操作,沒搶到就先等著。這裡付出了些代價,但保證了操作的安全可靠性。那麼這些保護是有必要的嗎?再觀察一下前面的**,多個執行緒有可能,有需要同時修改的變數就乙個而已: top. 只要我們參保證 top 在多執行緒的環境裡能夠安全地被修改,那對整個 stack 的修改也都是安全的。事情看起來,好像比較簡單。要保證對 top 變數的原子操作,我們需要 cpu 提供一些特殊的支援,來保證我們在對某些記憶體進行修改時,不會被執行緒所中斷,它要麼就完成,要麼就不完成,而不會在完成到一半時,被別的執行緒中斷。在 intel 平台上,從 80486 開始,cmpxchg 彙編指令可以幫助我們完全這件事情,這就是我們通常所說 cas 操作的基礎。

下面我們嘗試用 cas 來寫乙個無鎖的 stack.  

1

// 無鎖

的棧。2

3 typedef elem int;4

#define max (2048)56

static

elem stack[max];

7static

int top = 0;8

9bool

push(elem val)

10while(1

);22

23 stack[old_top] =val;

2425

return

true;26

}272829

bool pop(elem&val)

30 while(1

);44

4546

return

true

;47 }

上面的實現乍看起來很美好, 它會是我們想要的東西嗎?        

lockFreeQueue 無鎖佇列實現與總結

在工程上,為了解決兩個處理器互動速度不一致的問題,我們使用佇列作為快取,生產者將資料放入佇列,消費者從佇列中取出資料。這個時候就會出現四種情況,單生產者單消費者,多生產者單消費者,單生成者多消費者,多生產者多消費者。我們知道,多執行緒往往會帶來資料不一致的情況,一般需要靠加鎖解決問題。但是,加鎖往往...

無鎖佇列的實現

可以用cas 以及fetch等原子操作來實現無鎖的佇列,說是無鎖其實感覺也是有鎖的,只是鎖的力度比較小,能提公升效能 bool cas t addr,t expected,t newvalue else return false 使用陣列來實現佇列是很常見的方法,因為沒有記憶體的分部和釋放,一切都會...

無鎖佇列的實現 小結

原文見 cool shell cas,即lock free的一種實現,是構成jdk concurrent包高效併發容器的重要基礎,因此認真閱讀了一下。讀後附註 對aba問題的理解 在db裡,根據一行記錄中 最後修改時間 欄位的值是否變更,作為更新該行記錄時的參照物,是平時常見的一種做法,類似樂觀鎖。...