世界上最簡單的無鎖雜湊表

2021-07-10 08:03:55 字數 4593 閱讀 1238

無鎖雜湊表(lock-free hash table )可以提高多執行緒下的效能表現,但是因為實現乙個無鎖雜湊表本身的複雜度不小。(ps:真正的複雜在於出錯之後的除錯,因為多執行緒下的除錯本身就很複雜,引入無鎖資料結構之後,傳統的看堆疊資訊和列印log都基本上沒有意義了。堆疊中的資料可能被併發訪問破壞,而列印log本身可能會改變程式執行時對資料訪問的時序。乙個比較可行的做法是實現乙個無鎖版本和乙個傳統資料結構+鎖的版本,出錯後通過替換來定位是無鎖資料結構本身的bug還是其他邏輯的bug)。所以對乙個專案而言,無鎖資料結構基本上是一把雙刃劍。

很幸運,6年時間足夠我理解dr. cliff click所做的研究。事實上,你不必做一些前沿的探索,去實現乙個完美的無鎖雜湊表。在這裡我將分享我實現的這個版本。我相信有使用c++進行多執行緒開發經驗的程式設計師,可以通過這篇部落格梳理以前的經驗,並且完全理解它。

作為乙個程式設計師,平時我們實現乙個資料結構會本能的盡可能通用。這不是一件壞事,但是當我們把通用當作乙個更重要的目標時,它可能會阻礙我們。在這裡我走向另乙個極端,實現了乙個盡可能簡單的,僅用於一些特殊環境的雜湊表,下面是它的設計約束:

table 只接受型別為32位整數的key和value

所有key必須非零

所有的value必須非零

table的最大數目固定且必須是2的冪

唯一可用的操作是setitem和getitem

有沒有刪除操作

當然你掌握了這種演算法實現機制之後,可以在此基礎上進行擴充套件,而不受這些限制的約束。(rehash,刪除和遍歷,這些都會增加複雜度,而且有引發新的aba問題的可能性)。

有很多種方法來實現乙個雜湊表。這裡我選擇了用我以前的帖子中描述的arrayofitems類做乙個簡單的修改,(前置擴充套件閱讀)a lock-free… linear search?

這個雜湊表被我稱為hashtable1,和arrayofitems一樣,它採用了乙個巨大的key-value pairs陣列實現。

1

2

3

4

5

6

structentry

;

entry *m_entries;

在hashtable1中,僅僅只有陣列本身而沒有使用鏈結來處理碰撞。陣列全部初始化為0,key為0時對應的節點為空。插入時,會通過線性搜尋找到乙個空節點。

arrayofitems和hashtable1之間唯一的區別是,arrayofitems是從0開始做線性搜尋,而hashtable1使用murmurhash3′s integer finalizer演算法得到乙個hash值,然後以這個hash值為起點開始搜尋()

1

2

3

4

5

6

7

8

9

inlinestaticuint32_t integerhash(uint32_t h)

當我們使用相同的key做引數呼叫setitem或getitem方法時,它會在相同的index開始做線性搜尋,而使用不同的key時,會在不同的index開始搜尋。通過這種方式,可以提高查詢到對應key所在節點的速度,並且保證多執行緒併發呼叫setitem或getitem的安全性。

setitem的實現。它會掃瞄整個陣列並且將value儲存在與key對應的節點或空節點。這段**與arrayofitems:: setitem幾乎相同,唯一的區別是計算了hash值並且按位與,保證index在陣列邊界內。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

voidhashtable1::setitem(uint32_t key, uint32_t value)

}

}

getitem的實現也同樣和arrayofitems::getitem有類似的改變。

1

2

3

4

5

6

7

8

9

10

11

12

13

uint32_t hashtable1::getitem(uint32_t key)

}

最後,就像在以前的帖子中,我們可以優化setitem,第一次判斷是否可以避免使用cas操作。如下這種優化,可以使示例應用程式執行快大約20%。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

voidhashtable1::setitem(uint32_t key, uint32_t value)

// store the value in this array entry.

mint_store_32_relaxed(&m_entries[idx].value, value);

return;

}

}

乙個忠告:與arrayofitems一樣,hashtable1上的所有操作都採用了relaxed memory ordering做限制。因此,當在hashtable1中設定標記,共享一些資料供其他執行緒訪問時,必須事先插入release fence。同樣訪問資料的執行緒在呼叫getitem前需要acquire fence。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// shared variables

charmessage[256];

hashtable1 collection;

voidpublishmessage()

**放在github上,你可以自己編譯和執行。編譯說明見readme.md

在hashtable1沒有滿時—少於80%時—hashtable1表現的很好,我也許應該為這個說法做一些基準測試。但是以以往的常規的雜湊表作為基準,我敢肯定你很難實現出效能更好的無鎖雜湊表。這似乎奇怪,hashtable1基於arrayofitems,看起來會很低效。當然,任何雜湊表中,總會有發生碰撞的風險,而降階到arrayofitems的風險並不為0。但是使用乙個足夠大的陣列和類似murmurhash3這樣的hash函式,這種情況出現的很少。

在實際的工作中,我已經使用了乙個和這個類似的hash-table。這是乙個遊戲開發的專案,我的工作是解決使用記憶體分配跟蹤工具(memory tracker)之後對乙個讀寫鎖激烈的爭用。遷移到無鎖雜湊表的過程非常棘手。相對hashtable1需要更複雜的資料結構,key和value都是指標而不是簡單的整數。因此有必要在雜湊表內部插入memory ordering。最終在此模式下執行:最壞情況下遊戲的幀率提高了4-10 fps。

世界上最簡單的無鎖雜湊表

無鎖雜湊表 lock free hash table 可以提高多執行緒下的效能表現,但是因為實現乙個無鎖雜湊表本身的複雜度不小。ps 真正的複雜在於出錯之後的除錯,因為多執行緒下的除錯本身就很複雜,引入無鎖資料結構之後,傳統的看堆疊資訊和列印log都基本上沒有意義了。堆疊中的資料可能被併發訪問破壞,...

世界上最複雜的函式 世界上什麼貨幣最值錢?

世界上什麼貨幣最值錢?這點可能大多數人都會說歐元 美元之類的貨幣。但是現在,本文將給大家普及下世界上最值錢的貨幣是什麼。請往下看。世界上最值錢的貨幣是科威特的第納爾,它值22.8675元人民幣。除此之外,世界上前十的貨幣名單為 巴林第納爾 阿曼里亞爾 約旦第納爾 英鎊 開曼元 歐元 瑞士法郎 美元 ...

世界上最牛的演講

界上最牛的演講 oracle 甲骨文 的ceo larry.ellison在耶魯大學2000屆畢業典禮上的演講 耶魯的畢業生們,我很抱歉 如果你們不喜歡這樣的開場白。我想請你們為 我做一件事。請你 好好看一看周圍,看一看站在你左邊的同學,看一看站在你右 邊的同學。請你設想這樣的情況 從現在起5年之後...