# 2022q1 Homework5 (quiz6)
contributed by < [cyrong](https://github.com/cyrong) >
## 測驗 `1`
## 測驗 `2`
### `free_later.c`
一個 linked list 的實作在最前面
其中包含 `list_new`, `list_add` 兩個功能
`free_later_t`
```c
typedef struct {
void *var;
void (*free)(void *var);
} free_later_t;
```
這邊的 `free_later_t` 則是用來儲存之後需要被釋放的資料,而這些資料會先被放在一個 linked list `buffer` 中
在之後呼叫 `free_later_exit` 時會將儲存在 `buffer` 中的資料釋放掉
### `hashmap.c`
`hashmap_kv_t`
```c
typedef struct hashmap_keyval {
struct hashmap_keyval *next;
const void *key;
void *value;
} hashmap_kv_t;
```
這個結構用來 link 在每個 bucket 使用的 linked list
`hashmap_t`
```c
typedef struct {
hashmap_kv_t **buckets;
uint32_t n_buckets;
uint32_t length; /* total count of entries */
/* pointer to the hash and comparison functions */
uint64_t (*hash)(const void *key);
uint8_t (*cmp)(const void *x, const void *y);
/* custom memory management of internal linked lists */
void *opaque;
hashmap_kv_t *(*create_node)(void *opaque, const void *key, void *data);
void (*destroy_node)(void *opaque, hashmap_kv_t *node);
} 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 中的行為。
```c
bool hashmap_put(hashmap_t *map, const void *key, void *value)
{
if (!map)
return NULL;
/* hash to convert key to a bucket index where value would be stored */
uint32_t bucket_index = map->hash(key) % map->n_buckets;
hashmap_kv_t *kv = NULL, *prev = NULL;
/* known head and next entry to add to the list */
hashmap_kv_t *head = NULL, *next = NULL;
while (true) {
/* copy the head of the list before checking entries for equality */
head = map->buckets[bucket_index];
/* find any existing matches to this key */
prev = NULL;
if (head) {
for (kv = head; kv; kv = kv->next) {
if (map->cmp(key, kv->key) == 0)
break;
prev = kv;
}
}
if (kv) { /* if the key exists, update and return it */
if (!next) /* lazy make the next key-value pair to append */
next = map->create_node(map->opaque, key, value);
/* ensure the linked list's existing node chain persists */
next->next = kv->next;
/* CAS-update the reference in the previous node */
if (prev) {
/* replace this link, assuming it has not changed by another
* thread
*/
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)) {
/* this node, key and value are never again used by this */
map->destroy_node(map->opaque, kv);
return true;
}
hashmap_put_replace_fail += 1;
} else { /* no previous link, update the head of the list */
/* set the head of the list to be whatever this node points to
* (NULL or other links)
*/
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;
}
/* failure means at least one new entry was added, retry the
* whole match/del process
*/
hashmap_put_head_fail += 1;
}
} else { /* if the key does not exist, try adding it */
if (!next) /* make the next key-value pair to append */
next = map->create_node(map->opaque, key, value);
next->next = NULL;
if (head) /* make sure the reference to existing nodes is kept */
next->next = head;
uint32_t orig_tag = map->tag;
uint32_t next_tag = orig_tag + 1;
/* prepend the kv-pair or lazy-make the bucket */
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;
}
/* failure means another thead updated head before this.
* track the CAS failure for tests -- non-atomic to minimize
* thread contention
*/
hashmap_put_retries += 1;
}
}
}
```
## Lock-Free Linked Lists and Skip Lists 論文探討
〈[Lock-Free Linked Lists and Skip Lists](http://www.cse.yorku.ca/~ruppert/papers/lfll.pdf)〉
一般的 linked list 在 concurrent 操作下,delete node 可能會發生問題
使用 compare and swap (C&S) 以實現 deletion 時需確保被刪除的 node 的 next pointer 不會被其他執行緒改變,否則會發生錯誤。
論文中加入兩個 bit 在 next 欄位中
1. mark : 標記將要被 delete 的 node
2. flag : 作為一個警告,此 node 的 next 即將要被刪除
mark, flag 不會同時被設定。
並還有在資料結構中加入 backlink 以便 mark, flag 的操作。
每個執行緒偵測到 mark, flag bit 被設定都可以幫忙對應的操作。