<style> .reveal .slides { text-align: left; font-size:30px } </style> # ABA problem by 01157116 劉元楷 --- ## problem 在多線程的同步處理中,ABA problem 是一個比較常見的問題 (a)兩個同步處理的線程,在什麼情況下可能會發生ABA? (b)提出其中一種解決方法 --- ## 背景故事: 共享memory - **Threads** 可以: - 使用**共享記憶體**向另一個執行緒傳遞訊息 - **競爭相同的記憶體資源** - **記憶體存取**需要**同步(synchronisation)**,透過`lock`機制達成: - **Mutex** - **Semaphore** ---- ## lock 缺點 傳統的lock機制有時無法滿足效能需求,因為: - Deadlocks - Priority Inversion - Convoying ---- ## lock free 定義:在任何執行緒排程決策下都不應發生鎖死 可以理解成:只要給定足夠多的時間,保證程式中的某些執行序能夠有進展(call return) ---- ## how to do lock free? => `Atomic Operations` 將lock的作用域缩小到僅包含一個CPU指令 如: - Load/Store - Increment/Decrement - `Compare-And-Swap(CAS)` ---- ## 舉例 把 `X` 的值從 `a` 變成 `b` ### lock-base ```clike acquire(mutex_X) if X == a: X = b release(mutes_X) ``` ### lock free ```clike compare_and_swap(X,a,b) ``` --- ABA problem 發生在有多個線程對同一個記憶體位置進行寫操作時。 - 線程 1 從記憶體位置 `X` 讀取值 `A` - 線程 2 將值 `B` 存入 `X` - 線程 2 再將值 `A` 存入 `X` - 線程 1 從 `X` 讀取值 `A`,並假設未發生任何修改 ---- ## 舉例: lock free stack **Node:** ![image](https://hackmd.io/_uploads/Sk2smJJe1g.png =200x) - head: `atomic` Node * - push(data) - pop() ---- ## Lock free stack - Push 步驟 1. 創建新節點 2. 把next指標指到目前的head 3. 把head指到新節點的位置 ---- Thread A ```clike= Create a new node node.next = head head = &node ``` Thread B ```clike= Create a new node node.next = head head = &node ``` 問題: `head` 變了但是Thread A 不知道! ---- lock free push ```clike= function push(data): Node new_node = new Node(data) while True: new_node.next = head Node* old_head = new_node.next //不可從head拿因為可能被其他thread更改 if compare_and_swap(head,old_head,&new_node): break ``` 每次迴圈CAS若失敗,則代表head被更改,會重新執行pop邏輯 ---- ## Lock free stack - Pop 步驟 1. 讀取並檢查head的值 若為NULL則 等待其他thread push/return noting/throw exception 2. 把 head 設為 head -> next 3. 回傳 data 4. delete data --- lock free pop ```clike= function pop(): Node* old_head, new_head while True: old_head = head if old_head == NULL: continue new_head = old_head->next//這邊會導致ABA problem if compare_and_swap(head, old_head, new_head): break data_cpy = old_head->data free old_head return data_copy ``` 若在`new_head = old_head->next`時其它thread pop了,就會導致segmentation fault ---- 再回頭看: ABA problem 發生在有多個線程對同一個記憶體位置進行寫操作時。 - 線程 1 從記憶體位置 `X` 讀取值 `A` - 線程 2 將值 `B` 存入 `X` - 線程 2 再將值 `A` 存入 `X` - 線程 1 從 `X` 讀取值 `A`,並假設未發生任何修改 ---- ## 由範例來看ABA problem 假設目前有兩個Threads A, B 目前Stack: C <- B <- A (head is A). ABC為addresses ### Thread1 ```clike!= execute pop() read A from head read A -> next CAS(head,A,B) succeeds head is B which is already freed => corrupt ``` ### Thread2 ```clike= pop()// A is popped. Stack: C<-B pop()// B is popped. Stack: C push(data)// memory reuses address A Stack becomes C<-A ``` --- ## 解決方法 ---- ## Deferred reclamation(延遲回收) ### 核心思想 僅在確定没有其他threads引用某Node時,才回收(free)該Node ### 解决問题 通過避免過早釋放內存,防止其他threads存取已釋放的内存,解决 ABA problem 潛在的錯誤 ---- ### 實作方式 Hazard pointers 其基本的概念是允許 hazard pointer 可以被一個 thread 寫而多個 thread 讀,當存在 reader thread 正在操作該 hazard pointer 時,hazard pointer 會被標註,於是 writer thread 可得知此訊息並延遲對其釋放 ---- 在 hazard pointer 架構中,每個 thread 首先需要各自維護的兩個關鍵集合是: hazard pointer: 儲存此 thread 正在存取的指標,因此該指標不能直接被釋放 retire list: 被這個 thread 要求釋放的指標,但實際尚未釋放 ![image](https://hackmd.io/_uploads/By_Ilwke1x.png) ---- 因此要安全的釋放記憶體,其基本想法就是: 每個執行緒放在 hazard pointer 中的指標尚不能被釋放 每個執行緒要求釋放的指標先放在 retire list 中 掃描 retire list 可以得知所有執行緒皆不使用的指標,則可將其真正的釋放給作業系統 --- ## 參考資料 https://www.youtube.com/watch?v=Yl8Or0afcfg&t=237s https://hackmd.io/@sysprog/concurrency/%2F%40sysprog%2Fconcurrency-hazard-pointer
{"description":"by 01157116 劉元楷","title":"ABA problem","contributors":"[{\"id\":\"a4015e4c-07e3-4e29-95a7-0f01997dd684\",\"add\":3857,\"del\":87}]"}
    216 views