目的: 檢驗學員對 Atomics 操作, probabilistic data structure, Hazard pointer 的認知
作答表單: 測驗 1 (針對 Linux 核心「設計」課程)
作答表單: 測驗 2-3 (針對 Linux 核心「實作」課程)
1
延伸第 6 週測驗題涵蓋的 bloom filter,我們想發展得以在並行環境運作的程式碼。理論與實際性能對比,其中 k=2 與 n=2^28,理論錯誤率採用公式 計算得出。使用 k=2 是因為可將一個 64 位元雜湊分割成兩部分來來實作。要達到約 0.05 的錯誤率,需要設定 。雜湊函數使用 xxHash
原始程式碼 (部分遮蔽)
編譯方式:
參考執行輸出:
作答規範:
atomic_
開頭的 C11 Atomics 函式get
函式延伸問題:
2
在並行程式設計中,當我們在存取共用的記憶體物件時,需要考慮到其他執行緒是否有可能也正在存取同一個物件,若要釋放該記憶體物件時,不考慮這個問題,會引發嚴重的後果,例如 dangling pointer。
使用 mutex 是最簡單且直觀的方法:存取共用記憶體時,acquire lock 即可保證沒有其他執行緒正在存取同一物件,也就可安全地釋放記憶體。但若我們正在存取的是一種 lock-free 資料結構,當然就不能恣意地使用 lock,因為會違反 lock-free 特性,即無論任何執行緒失敗,其他執行緒都能可繼續執行。於是乎,我們需要某種同為 lock-free 的記憶體物件回收機制。
lfq
嘗試實作精簡的並行佇列 (concurrent queue),運用 hazard pointer 來釋放並行處理過程中的記憶體,原始程式碼 (部分遮蔽)。
在 lfq.h
中,先定義二個結構體。
struct lfq_node
是 lock-free queue node 的結構,其中除了資料以及代表能否 free 的一個 bool 以外,還嵌入 union 的型別去儲存 next
及 free_next
,next
是佇列中的下一個節點,free_next
則是 free pool 中的下個節點。
struct lfq_ctx
是 lock-free queue handler 的結構,儲存佇列及 free pool 的頭尾,及 tid 的 map,還有個 bool 型態的變數用來確認是否正在做 free 的動作,此外,也儲存 hazard pointers 和他的最大長度。
lfq_enqueue
的功能為推入 (push) 資料進入佇列中,作法是將佇列的尾端替換成 insert_node
,再將原先舊尾端 old_tail
的 next
指向 insert_node
。
lfq_dequeue
的功能為將資料從佇列中取出 (pop),首先,會先透過 alloc_tid
取得 tid ,如果順利取得非 -1
的值,就會透過 lfq_dequeue_tid
去做 pop,在 lfq_dequeue_tid
中,會先將要 pop 的 old_head
存入 ctx->HP[tid]
之中,然後將佇列的 head
替換成 new_head
也就是 old_head->next
,完成後再將 ctx->HP[tid]
重設回 0,再對 old_head
做 safe_free
。
若節點能被 free 且不在 hazard pointers 內,safe_free
函式嘗試釋放已配置的資源,否則就藉由 insert_pool
只將節點加入 free pool,倘若 CAS(&ctx->is_freeing, 0, 1)
操作成功的話,就能確實地釋放節點佔用的記憶體空間,但若失敗,就一樣只將節點加入 free pool,最後呼叫 free_pool
以嘗試釋放 free pool 內的節點。
編譯方式:
這個實作應該要能夠支援以下組態:
-D MAX_PRODUCER=4 -D MAX_CONSUMER=4
-D MAX_PRODUCER=100 -D MAX_CONSUMER=10
-D MAX_PRODUCER=10 -D MAX_CONSUMER=100
預期輸出:
補完程式碼,使其運作符合預期。作答規範:
AAAA
, BBBB
, CCCC
均為傳入 atomic_compare_exchange_strong
的參數,採用作業一規範的程式碼書寫風格延伸閱讀:
延伸問題:
3
對應到 並行程式設計: Thread Pool 實作和改進 和 Linux 核心設計: 針對事件驅動的 I/O 模型演化
httpd
是個運用 thread pool 和 epoll 系統呼叫的網頁伺服器,原始程式碼 (部分遮蔽)。
書寫規範:
延伸問題: