併發資料結構-1.6 雜湊表
典型可擴充套件的雜湊表即一個可調整大小的桶陣列(buckets), 每一個桶存放預期數量的元素,因此雜湊表平均在常量時間內進行插入,刪除,查詢操作。雜湊表調整大小的主要成本—–在於新舊桶(buckets)之間進行重新分配操作,該操作被分攤到所有表操作上,所以平均操作時間也是常量的。雜湊表調整大小就是擴容,在實踐中,雜湊表僅需要增加陣列大小即可。
Michael實現了一個可併發,不可擴充套件的雜湊表(通過對雜湊表中每個桶進行讀寫鎖約束)。然而,為了保證元素數量增長時的效能,雜湊表必須可擴充套件。
在80年代,Ellis和其他人基於二級鎖機制,為分散式資料庫設計了一個可擴充套件且併發的雜湊表。Lea的可擴充套件的hash演算法在非併發環境下表現出了高效能,該演算法是基於Litwin的線性序列hash演算法,它用了不同的加鎖方案:用少量的獨佔鎖代替原本對每一個桶都加鎖,並且當調整雜湊表大小時,允許併發查詢操作,但不能併發插入或刪除。當雜湊表大小需要擴充套件為2倍時,調整大小表現為對所有內部桶(buckets)進行重構。
正如之前討論的,基於鎖的可擴充套件的雜湊表演算法同樣具有阻塞同步所帶來的典型的缺點。這些問題會由於對雜湊表所有新新增桶(buckets)進行重分配變得更嚴重。因此無鎖可擴充套件的雜湊表是一個既實際又理論的問題。
如1.5節描述的,Michael在Harries工作之上,提供了一個有效的,基於CAS,無鎖的連結串列實現。然後將此原理運用到併發環境下效能不錯的無鎖的hash結構中:一個固定大小的hash桶陣列,每個桶由無鎖鏈表實現。但是,要使一個無鎖的連結串列陣列可擴充套件很困難,因為當桶(buckets)陣列擴充套件時,要在無鎖方式下重新分配元素不是很容易的。在兩個不同桶連結串列之間移動元素需要兩個CAS操作同時原子地完成,這點在目前的體系結構中是不可能做到的。
Greenwald展示了怎麼用他的雙手模擬技術(two-handed emulation)來實現一個可擴充套件的雜湊表。然而,這種技術使用了DCAS同步操作,該操作在當今的架構中不可用,並且在全域性調整大小時會帶來過多的工作。
Shalev和Shavit在當今架構下提出了一種無鎖可擴充套件的雜湊表。他們的核心思想在於將元素放在單個無鎖鏈表中,而不是每個桶(bucket)中的連結串列。為了讓操作能夠快速訪問連結串列, Shalev-Shavit演算法維護了一個可調整大小的hints陣列(指向連結串列的指標),相關操作通過hints陣列中的指標找到接近相關元素的位置,然後順著該指標找到元素位置。為了保證每個操作平均能在常量步驟內完成,當連結串列中的元素個數增長時,細粒度的hints陣列必須要新增。為了使hints陣列能簡單有效地被裝配,連結串列由一個遞迴分割順序來維護。該技術使得新的hints能夠增量裝配,從而消除了原子地在桶(bucket)之間移動元素或重新排序列表帶來地複雜性需求。