contributed by < ambersun1234
>
本次實驗程式碼是以 C 語言 atomic operation 以及 futex 模擬 go routine 以及 go channel
首先先來看看什麼是 futex
context switch
到 kernel space),因此 futex
的誕生可以讓 locking 這件事在 user space
之中完成,所以理論上會比一般的 mutex
, semaphore
還要來的快速futex
的出現僅是為了減少 system call 的消耗,並非完全由 user space 控制。根據 futex(7) 所述
Futex operation occurs entirely in user space for the
noncontended case. The kernel is involved only to arbitrate the contended case. As any sane design will strive for
noncontention, futexes are also optimized for this situation.
不難發現到實做中有許多用到 futex
的地方
go routine
以及 go channel
,自然就會有多個執行緒存在的情況。以上述程式碼為例,就是要從 ring buffer 中獲取資料,可以看到 futex_wake
以及 futex_wait
的存在4 bytes
長的整數,用法如下
up a futex
0 變 1
代表沒有任何的 waiterfutex_wake
)wait a futex
futex_wait
)ring buffer
裡面寫資料atomic_load_explicit
) 以及比較並交換 (CAS, atomic_compare_exchange_weak_explicit
) 都必須使用 atomic operation 以保證其的正確性explicit
,該位置是定義 memory order
memory order
,考慮以下官網範例 cppreference/memory_order以上結果有可能會是 r1 == r2 == 42
或其他,為什麼說可能呢?因為編譯器優化會微調 instruction 位置,有可能導致非預期行為
本程式用了兩種 order 方式
memory_order_relaxed
memory_order_release
A store operation with this memory order performs the release operation: no reads or writes in the current thread can be reordered after this store. All writes in the current thread are visible in other threads that acquire the same atomic variable (see Release-Acquire ordering below) and writes that carry a dependency into the atomic variable become visible in other threads that consume the same atomic (see Release-Consume ordering below).
memory_order_acquire
memory_order_release
同樣概念,套用在 load operation 上應提及 lock-free programming 的設計考量,如下方的 lap
:notes: jserv
unbuffer
以及 ring bufferlap
以及 data
data
: 即資料存放位置lap
: 類似一個計數器(用以紀錄)滿了
還是 空的
實際上是有一點困難的。單純的判斷 head == tail
就會有上述問題lap
用以紀錄讀寫狀態,值得注意的是為了節省資料空間,lap
以及 pos
是放在 uint64_t
底下,在需要讀取的時候分別用 bitwise
運算取出;而此作法很剛好的滿足了 atomic function 的參數需求(uint64_t
)lap
的數值而定,假設 lap
數值相同即代表可以將資料寫入 ring buffer 當中,若不相同則代表 ring buffer 已經滿了channel->tail
會被改為 (uint64_t)(lap+2) << 32
pos
? 因為重新一輪的位置一定是從0開始,只要紀錄新 lap 即可head, tail
的 lap 不會跟 item
的 lap 相同,由上述規律我們可以得知
2n
的時候 \(\to\) 寫入資料2n + 1
的時候 \(\to\) 讀取資料-fsanitize=thread --ggdb -fPIE -pie
即可開啟檢查,會有以下錯誤unbuffered channel
中,於是我嘗試使用 atomic_exchange_explicit
, atomic_store_explicit
保護資料,結果都是以失敗告終send_unbuf
以及 recv_unbuf
來看可以發現到一開始都是先檢查 data pointer 是否為 NULL,兩者分別做存取以及寫入的動作data
這個變數導致的,即使我加上了 atomic 進行存取還是會導致 data race(詳見底下測試)0x7f6e6bbbd278
這個位置linux2021