# 2021q3 Homework2 (hp) contributed by < `fwfly` > ## hp 測試設計 建立 n 個 thread 去對一個 list 做 insert key 然後 n 個 thread 去同一個 list 做 delete key 這樣會同時對一個 list 做新增,刪除跟搜尋(find)而導致 race condition 的問題 最後再用 hp 的方式達到 lock-free 的解法 ### Elements 的設計 程式用了 2 維 array 去產生 key. 這個 array 可以記錄給個 insert thread 產生的 key 在 delete 的時候, delete thread 也可以根據拿到的 tid 從 array 中找到 key ##### Bug - delete thread 可能沒有刪到任何的 key 在 main 裡面可以看到 thread 是輪流去產生 delete 跟 insert thread, 所以 delete 跟 insert thread 會有同樣的數目,理想上兩邊剛好可以抵消 ```cpp pthread_create(&thr[i], NULL, (i & 1) ? delete_thread : insert_thread, list); ``` 但是問題出在這邊 ```cpp // 這是 insert thread (void) list_insert(list, (uintptr_t) &elements[tid()][i]); // 這是 delete thread (void) list_delete(list, (uintptr_t) &elements[tid()][i]); // 這是 tid 的取得方式, 有新的 thread 就給一個 +1 的 tid tid_v = atomic_fetch_add(&tid_v_base, 1); ``` 從上面可以知道 key 是 elements 中每一個 cell 的 address 最後產生的 elements 會是下圖 ``` // 這是 elements tid i insert 0 1,2,3.......max_elements delete 1 1,2,3.......max_elements insert 2 1,2,3.......max_elements delete 3 1,2,3.......max_elements insert 4 1,2,3.......max_elements ..... ``` 可以看到 delete 跟 insert 是交錯的關係, 所以 delete 永遠不會刪到在 list 中的任何一個 key, 可以簡單地補上 printf 驗證 ```cpp bool list_delete(list_t *list, list_key_t key) { printf("LST_DEL%d: %ld\n", tid(), key); list_node_t *curr, *next; atomic_uintptr_t *prev; while (true) { if (!__list_find(list, &key, &prev, &curr, &next)) { list_hp_clear(list->hp); // 請不要把 printf 加在 list_hp_clear 前面, 不確定是否會影響到 hp 的運作 printf("LST_DEL%d: %ld ,res: False\n", tid(), key); return false; } printf("LST_DEL%d: %ld ,res: True\n", tid(), key); uintptr_t tmp = get_unmarked(next); ``` 然後會發現程式都只會印出 False 直到最後一段把 list 全部清除時才會印到 True 這時候才會做到真正的刪除. 所以如果加上這段 printf, 會發現 True 全部集中在最後一段 ``` LST_DEL1: 94799338001472 ,res: False ... ... LST_DEL63: 94799338065928 ,res: False LST_DEL63: 94799338065936 ,res: False LST_DEL63: 94799338065944 ,res: False LST_DEL63: 94799338065952 ,res: False LST_DEL63: 94799338065960 ,res: False LST_DEL63: 94799338065968 ,res: False LST_DEL63: 94799338065976 ,res: False Delete all LST_DEL64: 94799338000448 LST_DEL64: 94799338000448 ,res: True LST_DEL64: 94799338001472 LST_DEL64: 94799338001472 ,res: False LST_DEL64: 94799338002496 LST_DEL64: 94799338002496 ,res: True LST_DEL64: 94799338003520 LST_DEL64: 94799338003520 ,res: False LST_DEL64: 94799338004544 LST_DEL64: 94799338004544 ,res: True LST_DEL64: 94799338005568 LST_DEL64: 94799338005568 ,res: False LST_DEL64: 94799338006592 ``` #### patch 用兩個 tid_base 分別計算 insert thread 跟 delete thread 因為 elements 是用 idx 去找 key, 所以並不影響原本的程式. [Patch 的程式碼](https://github.com/fwfly/concurrent-programs/commit/6262ca154a67126a4d968c1d0f75f343b300c145) 經過上面的 patch 後的程式,兩種 thread 確實產生了 race condition, 但是觸發了 ThreadSanitizer: data race 的 error. ```cpp $ ./list LST_DEL0: 93989954416704 LST_DEL0: 93989954416704 ,res: True LST_DEL0: 93989954416712 LST_DEL0: 93989954416712 ,res: True ================== WARNING: ThreadSanitizer: data race (pid=275662) Write of size 8 at 0x7b4000010180 by thread T2: #0 free ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:707 (libtsan.so.0+0x35f25) #1 list_node_destroy /home/fwfly/project/concurrent-programs/hp_list/main.c:325 (list+0x21af) #2 __list_node_delete /home/fwfly/project/concurrent-programs/hp_list/main.c:332 (list+0x2209) #3 list_hp_retire /home/fwfly/project/concurrent-programs/hp_list/main.c:274 (list+0x1d39) #4 list_delete /home/fwfly/project/concurrent-programs/hp_list/main.c:440 (list+0x269e) #5 delete_thread /home/fwfly/project/concurrent-programs/hp_list/main.c:497 (list+0x27df) Previous atomic read of size 8 at 0x7b4000010180 by thread T1: #0 __tsan_atomic64_load ../../../../src/libsanitizer/tsan/tsan_interface_atomic.cpp:539 (libtsan.so.0+0x7ced7) #1 __list_find /home/fwfly/project/concurrent-programs/hp_list/main.c:365 (list+0x1fb4) #2 list_insert /home/fwfly/project/concurrent-programs/hp_list/main.c:401 (list+0x22c1) #3 insert_thread /home/fwfly/project/concurrent-programs/hp_list/main.c:487 (list+0x243f) Thread T2 (tid=275665, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x5ea79) #1 main /home/fwfly/project/concurrent-programs/hp_list/main.c:509 (list+0x13e0) Thread T1 (tid=275664, running) created by main thread at: #0 pthread_create ../../../../src/libsanitizer/tsan/tsan_interceptors_posix.cpp:962 (libtsan.so.0+0x5ea79) #1 main /home/fwfly/project/concurrent-programs/hp_list/main.c:509 (list+0x13e0) SUMMARY: ThreadSanitizer: data race /home/fwfly/project/concurrent-programs/hp_list/main.c:325 in list_node_destroy ``` 問題會發生在做 retire node 的部分,要先理解 retire 跟整個 hp 的設計,才能知道為什麼這邊會發生 ## 從基本的 list 開始 在 atomic 跟 hp 的幫助下,有點難看出來 atomic 跟 hp 解決了什麼事情, 所以把全部的 atomic 跟 hp 拿掉來實驗看看程式會出現什麼結果. * prev 用的是 atomic_uintptr_t 去接 node,這是為了要讓 prev 可以做 atomic 操作,但是還不清楚為什麼只有在 prev 上面做這件事情. ## 使用 atomic 來處理 list 中的值 ( To do ) ## 使用 hp 來處理 concurrent 問題 ( To do ) ## 並行程式設計中的問題 1. RMW 可能會可能無法完成全部. 可能在做 R 或 M 的途中就會被切到另一個 thread 然讀到舊的值,導致結果產生錯誤 -> atomic operation 可解決 2. 執行順序會被compiler 或 CPU 置換 -> 使用 memory barrier 來確保執行順序正確 3. cache coherence 問題 -> atomic 可以等 cache 同步然後寫入 memory,但是會有效能問題 ## Atomics 這次作業的 list.c 裡面的 hazard pointer 的機制是用 c11 的 atomic 去進行實作.所以再踏入 hazard pointer 前,要先了解一下 atomic 怎麼使用. 這次的實作有用到以下幾個 atomic 的 function atomic_store : 把值給寫進去 atomic object atomic_store_explicit : 跟 atomic_store 但是可以選擇怎麼處理 memory order atomic_load : 把 atomic 值從 atomic object 中讀出來 atomic_compare_exchange_strong : 比較 atomic object atomic 跟另一個參數,如果比較大就把 desired 值存到 atomic object 中 get_unmarked ? get_unmarked_node ? ## References * [2021 年暑期 Linux 核心 第 2 週測驗題](https://hackmd.io/@sysprog/linux2021-summer-quiz2) * [atomic_store, atomic_store_explicit](https://en.cppreference.com/w/c/atomic/atomic_store) * [memory_order](https://en.cppreference.com/w/c/atomic/memory_order) * [POSIX Shared Memory](http://logan.tw/posts/2018/01/07/posix-shared-memory/) * [Validating Memory Barriers and Atomic Instructions](https://lwn.net/Articles/470681) * [内存屏障实验](http://cxd2014.github.io/2018/05/22/memory-barrier/) * Linux Kernel Scheduler Internals - ch2 Multiprocessing * [Memory Barriers: a Hardware View for Software Hackers](http://www.rdrop.com/users/paulmck/scalability/paper/whymb.2010.07.23a.pdf) * [每位程式開發者都該知道的記憶體知識](https://sysprog21.github.io/cpumemory-zhtw/cpu-caches/cpu-caches-in-the-big-picture.html?q=) * [gcc tsan_interface_atomic.cpp](https://github.com/gcc-mirror/gcc/blob/master/libsanitizer/tsan/tsan_interface_atomic.cpp) * [ThreadSanitizer – data race detection in practice](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/35604.pdf) * [TSan: possible false positive with atomics](https://github.com/google/sanitizers/issues/1009) * [Hazard Pointers: Safe Memory Reclamation for Lock-Free Objects](https://www.cs.bgu.ac.il/~satcc112/wiki.files/HazardPointers.pdf) * [Lock-Free Data Structures with Hazard Pointers](https://erdani.org/publications/cuj-2004-12.pdf)