2022q1 Homework5 (quiz6)
contributed by < cyrong >
測驗 1
測驗 2
free_later.c
一個 linked list 的實作在最前面
其中包含 list_new
, list_add
兩個功能
free_later_t
這邊的 free_later_t
則是用來儲存之後需要被釋放的資料,而這些資料會先被放在一個 linked list buffer
中
在之後呼叫 free_later_exit
時會將儲存在 buffer
中的資料釋放掉
hashmap.c
hashmap_kv_t
這個結構用來 link 在每個 bucket 使用的 linked list
hashmap_t
這個結構是主要用的 hashmap,裝有各個 buckets 以及其 linked lists
對 hash map 進行修改的函式有兩個, hashmap_put
和 hashmap_del
hashmap_put
是將一組 key
, value
放進 hash map 中,而因為使用 atomic compare and swap,可能會因為其他 thread 導致操作失敗,分為兩種失敗
hashmap_put_replace_fail
: 輸入的 key
已經在 hash map 中存在,在進行替換 value
時操作失敗
hashmap_put_head_fail
: 輸入的 key
在 hash map 中不存在,而要將此 key
加入到 bucket list 時操作失敗(head
被其他 thread 修改)
hashmap_del
是將輸入的 key
從 hash map 中刪除,一樣可能因為 atomic 指令導致失敗
hashmap_del_fail
: key
不是 bucket list head 但存在於 list 中
hashmap_del_fail_new_head
: key
為 bucket list head
test-hashmap.c
test_add
重複操作 mt_add_vals
直到 hashmap_put_retries
!= 0
test_del
重複操作 mt_del_vals
直到 hashmap_del_fail
或是 hashmap_del_fail_new_head
!= 0
ABA problem
hashmap 中 put 和 del 操作涉及 map
內容的變更,若只是單純使用 CAS 來進行修改資料,可能會發生 ABA 問題,因此在 map 中加入一個新變數 tag
tag
用來指示 map
是否已經被修改,不論是任何修改 tag
都會改變,若是 tag
與原先不同則視為 put 失敗,將重新再執行一次 while loop 中的行為。
bool hashmap_put(hashmap_t *map, const void *key, void *value)
{
if (!map)
return NULL;
uint32_t bucket_index = map->hash(key) % map->n_buckets;
hashmap_kv_t *kv = NULL, *prev = NULL;
hashmap_kv_t *head = NULL, *next = NULL;
while (true) {
head = map->buckets[bucket_index];
prev = NULL;
if (head) {
for (kv = head; kv; kv = kv->next) {
if (map->cmp(key, kv->key) == 0)
break;
prev = kv;
}
}
if (kv) {
if (!next)
next = map->create_node(map->opaque, key, value);
next->next = kv->next;
if (prev) {
uint32_t orig_tag = map->tag;
uint32_t next_tag = orig_tag + 1;
if (__atomic_compare_exchange(&map->tag, &orig_tag, &next_tag,
false,
__ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST) &&
__atomic_compare_exchange(&prev->next, &kv, &next,
false,
__ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
map->destroy_node(map->opaque, kv);
return true;
}
hashmap_put_replace_fail += 1;
} else {
uint32_t orig_tag = map->tag;
uint32_t next_tag = orig_tag + 1;
if (__atomic_compare_exchange(&map->tag, &orig_tag,
&next_tag, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST) &&
__atomic_compare_exchange(&map->buckets[bucket_index], &kv,
&next, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
map->destroy_node(map->opaque, kv);
return true;
}
hashmap_put_head_fail += 1;
}
} else {
if (!next)
next = map->create_node(map->opaque, key, value);
next->next = NULL;
if (head)
next->next = head;
uint32_t orig_tag = map->tag;
uint32_t next_tag = orig_tag + 1;
if (__atomic_compare_exchange(&map->tag, &orig_tag,
&next_tag, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST) &&
__atomic_compare_exchange(&map->buckets[bucket_index], &head,
&next, false, __ATOMIC_SEQ_CST,
__ATOMIC_SEQ_CST)) {
__atomic_fetch_add(&map->length, 1, __ATOMIC_SEQ_CST);
return false;
}
hashmap_put_retries += 1;
}
}
}
Lock-Free Linked Lists and Skip Lists 論文探討
〈Lock-Free Linked Lists and Skip Lists〉
一般的 linked list 在 concurrent 操作下,delete node 可能會發生問題
使用 compare and swap (C&S) 以實現 deletion 時需確保被刪除的 node 的 next pointer 不會被其他執行緒改變,否則會發生錯誤。
論文中加入兩個 bit 在 next 欄位中
- mark : 標記將要被 delete 的 node
- flag : 作為一個警告,此 node 的 next 即將要被刪除
mark, flag 不會同時被設定。
並還有在資料結構中加入 backlink 以便 mark, flag 的操作。
每個執行緒偵測到 mark, flag bit 被設定都可以幫忙對應的操作。