併發資料結構-1.5 連結串列
考慮支援插入,刪除和查詢操作的併發資料結構實現。如果這些操作只處理鍵值(譯者注:而不處理具體值),這樣的資料結構會是一個集合。如果一個數據值與每一個鍵關聯起來,我們就得到了一部資料字典。由於他們都是密切相關的資料結構,一個併發的集合通常能夠經過適當修改來實現一部字典。在接下來的三個小節中,我們將專注於利用linked lists,hash tables,和trees這三種不同的資料結構來實現集合。
試想我們使用一個linked list去實現集合。除了鎖住整個linked list來避免併發操作以外,最普遍的實現基於鎖的linked lists的方式是hand-over-hand-locking(有時也叫lock coupling)。在這種方式下,每個節點都有一個關聯的鎖。一個正在遍歷Linked list的執行緒只有在獲取到了下一個節點的鎖時才會釋放上一個節點的鎖,這樣就避免了overtaking現象:可能導致執行緒未察覺節點被其他執行緒刪除。這種方式減小了鎖的粒度,但是由於插入和刪除操作在連結串列的不同位置可能相互阻礙,而限制了併發性.
一種解決這個問題的途徑就是設計出一個無鎖的 linked lists。實現出這樣一個無鎖的有序linked lists的困難在於要確保在插入或者刪除操作期間,相鄰的節點仍然是有效的,即他們仍然存在於列表當中並且是相鄰的。正如1.4節中圖1.6展現的一樣,設計出這樣一個無鎖的連結串列不是一件輕鬆的活。
圖1.6:基於CAS的連結串列操作是複雜的。在圖中的兩個例子(這兩個例子都濫用了CAS操作)中,P從連結串列中將b元素刪除。在上面的例子中,Q嘗試將c元素插入到連結串列中,在下面的例子中,Q元素嘗試將c元素從連結串列中刪除。圓圈標記的位置代表了CAS操作的目標地址,叉號標記的連結代表在CAS成功之前連結。
第一個基於CAS的無鎖鏈表是Valois 提出來的,他在每個普通節點之前使用了一個特殊的輔助節點來防止在圖1.6中發生的非期望現象。Valois的演算法在結合了Michael和Scott的記憶體管理方案之後是正確的,但是這種解決方式並不實用。Harris提出了一種使用了特殊的,帶有被原子訪問的“刪除”標記位的無鎖鏈表,用來標記該節點是否已經被刪除,然而此方案只有在垃圾收集環境下才是適用的。Michael 通過修改Harris的演算法使其相容記憶體回收方法,彌補了這個缺陷。