├── Makefile ├── README.md ├── common.h ├── haz_ptr.c ├── haz_ptr.h ├── l_list.c ├── l_list.h └── l_test.c /Makefile: -------------------------------------------------------------------------------- 1 | 2 | CFLAGS := -Wall -std=gnu99 -lpthread -g -DDEBUG 3 | 4 | all: 5 | gcc $(CFLAGS) *.c -o l_list 6 | 7 | clean: 8 | rm *.o l_list 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lock free order linked list sample implementation based on [[High Performance Dynamic Lock-Free Hash Tables](http://www.research.ibm.com/people/m/michael/spaa-2002.pdf)] 2 | 3 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __l_list_common_h 3 | #define __l_list_common_h 4 | 5 | #define CAS_V(addr, old, x) __sync_val_compare_and_swap(addr, old, x) 6 | #define CAS(addr, old, x) (CAS_V(addr, old, x) == old) 7 | #define ATOMIC_INC(addr) __sync_fetch_and_add(addr, 1) 8 | #define ATOMIC_ADD(addr, n) __sync_add_and_fetch(addr, n) 9 | 10 | #define TRUE 1 11 | #define FALSE 0 12 | 13 | #define trace printf 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /haz_ptr.c: -------------------------------------------------------------------------------- 1 | /* 2 | * a simple hazard pointer implementation 3 | * Kevin Lynx 2015.05.02 4 | */ 5 | #include "haz_ptr.h" 6 | #include "common.h" 7 | #include 8 | #include 9 | #include 10 | 11 | #define HAZ_MAX_COUNT HAZ_MAX_THREAD * HAZ_MAX_COUNT_PER_THREAD 12 | 13 | typedef struct free_t { 14 | void *p; 15 | struct free_t *next; 16 | } free_t; 17 | 18 | static int _tidSeed = 0; 19 | static hazard_t _haz_array[HAZ_MAX_COUNT]; 20 | /* a simple link list to save pending free pointers */ 21 | static free_t _free_list[HAZ_MAX_THREAD]; 22 | 23 | static int get_thread_id() { 24 | static __thread int _tid = -1; 25 | if (_tid >= 0) return _tid; 26 | _tid = ATOMIC_INC(&_tidSeed); 27 | _free_list[_tid].next = NULL; 28 | return _tid; 29 | } 30 | 31 | static int haz_confict(int self, void *p) { 32 | int self_p = self * HAZ_MAX_COUNT_PER_THREAD; 33 | for (int i = 0; i < HAZ_MAX_COUNT; ++i) { 34 | if (i >= self_p && i < self_p + HAZ_MAX_COUNT_PER_THREAD) 35 | continue; /* skip self */ 36 | if (_haz_array[i] == p) 37 | return TRUE; 38 | } 39 | return FALSE; 40 | } 41 | 42 | hazard_t *haz_get(int idx) { 43 | int tid = get_thread_id(); 44 | return &_haz_array[tid * HAZ_MAX_COUNT_PER_THREAD + idx]; 45 | } 46 | 47 | void haz_defer_free(void *p) { 48 | int tid = get_thread_id(); 49 | free_t *f = (free_t*) malloc(sizeof(*f)); 50 | f->p = p; 51 | f->next = _free_list[tid].next; 52 | _free_list[tid].next = f; 53 | haz_gc(); 54 | } 55 | 56 | void haz_gc() { 57 | int tid = get_thread_id(); 58 | free_t *head = &_free_list[tid]; 59 | free_t *pred = head, *next = head->next; 60 | while (next) { 61 | if (!haz_confict(tid, next->p)) { /* safe to free */ 62 | free_t *tmp = next->next; 63 | trace("hazard (%d) free ptr %p\n", tid, next->p); 64 | free(next->p); 65 | pred->next = tmp; 66 | free(next); 67 | next = tmp; 68 | } else { 69 | pred = next; 70 | next = next->next; 71 | } 72 | } 73 | if (head->next == NULL) { 74 | trace("thread %d freed all ptrs\n", tid); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /haz_ptr.h: -------------------------------------------------------------------------------- 1 | #ifndef __haz_ptr_h 2 | #define __haz_ptr_h 3 | 4 | typedef void* hazard_t; 5 | 6 | #define HAZ_MAX_THREAD 1024 7 | #define HAZ_MAX_COUNT_PER_THREAD 4 8 | 9 | hazard_t *haz_get(int idx); 10 | void haz_defer_free(void *p); 11 | void haz_gc(); 12 | #define haz_set_ptr(haz, p) (*(void**)haz) = p 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /l_list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lock-free order-list sample implementation based on 3 | * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf 4 | * Kevin Lynx 2015.05.02 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include "common.h" 10 | #include "l_list.h" 11 | #include "haz_ptr.h" 12 | 13 | typedef size_t markable_t; 14 | 15 | #define MASK ((sizeof(node_t) - 1) & ~0x01) 16 | 17 | #define GET_TAG(p) ((markable_t)p & MASK) 18 | #define TAG(p, tag) ((markable_t)p | (tag)) 19 | #define MARK(p) ((markable_t)p | 0x01) 20 | #define HAS_MARK(p) ((markable_t)p & 0x01) 21 | #define STRIP_MARK(p) ((node_t*)((markable_t)p & ~(MASK | 0x01))) 22 | 23 | int l_find(node_t **pred_ptr, node_t **item_ptr, node_t *head, key_t key); 24 | 25 | node_t *l_alloc() { 26 | node_t *head = (node_t*) malloc(sizeof(*head)); 27 | trace("alloc list %p\n", head); 28 | head->next = NULL; 29 | return head; 30 | } 31 | 32 | void l_free(node_t *head) { 33 | node_t *item = head->next; 34 | while (item) { 35 | node_t *next = STRIP_MARK(item->next); 36 | free((STRIP_MARK(item))); 37 | item = next; 38 | } 39 | free(head); 40 | } 41 | 42 | value_t l_insert(node_t *head, key_t key, value_t val) { 43 | node_t *pred, *item, *new_item; 44 | while (TRUE) { 45 | if (l_find(&pred, &item, head, key)) { /* update its value */ 46 | /* 更新值时,使用item->val互斥,NULL_VALUE表示被删除 */ 47 | node_t *sitem = STRIP_MARK(item); 48 | value_t old_val = sitem->val; 49 | while (old_val != NULL_VALUE) { 50 | value_t ret = CAS_V(&sitem->val, old_val, val); 51 | if (ret == old_val) { 52 | trace("update item %p value success\n", item); 53 | return ret; 54 | } 55 | trace("update item lost race %p\n", item); 56 | old_val = ret; 57 | } 58 | trace("item %p removed, retry\n", item); 59 | continue; /* the item has been removed, we retry */ 60 | } 61 | new_item = (node_t*) malloc(sizeof(node_t)); 62 | new_item->key = key; 63 | new_item->val = val; 64 | new_item->next = item; 65 | /* a). pred是否有效问题:无效只会发生在使用pred->next时,而l_remove正移除该pred 66 | 就会在CAS(&item->next)中竞争,如果remove中成功,那么insert CAS就失败, 67 | 然后重试;反之,remove失败重试,所以保证了pred有效 68 | b). item本身增加tag一定程度上降低ABA问题几率 69 | */ 70 | if (CAS(&pred->next, item, new_item)) { 71 | trace("insert item %p(next) %p success\n", item, new_item); 72 | return val; 73 | } 74 | trace("cas item %p failed, retry\n", item); 75 | free(new_item); 76 | } 77 | return NULL_VALUE; 78 | } 79 | 80 | int l_remove(node_t *head, key_t key) { 81 | node_t *pred, *item, *sitem; 82 | while (TRUE) { 83 | if (!l_find(&pred, &item, head, key)) { 84 | trace("remove item failed %d\n", key); 85 | return FALSE; 86 | } 87 | sitem = STRIP_MARK(item); 88 | node_t *inext = sitem->next; 89 | /* 先标记再删除 */ 90 | if (!CAS(&sitem->next, inext, MARK(inext))) { 91 | trace("cas item %p mark failed\n", sitem->next); 92 | continue; 93 | } 94 | sitem->val = NULL_VALUE; 95 | int tag = GET_TAG(pred->next) + 1; 96 | if (CAS(&pred->next, item, TAG(STRIP_MARK(sitem->next), tag))) { 97 | trace("remove item %p success\n", item); 98 | haz_defer_free(sitem); 99 | return TRUE; 100 | } 101 | trace("cas item remove item %p failed\n", item); 102 | } 103 | return FALSE; 104 | } 105 | 106 | int l_find(node_t **pred_ptr, node_t **item_ptr, node_t *head, key_t key) { 107 | node_t *pred = head; 108 | node_t *item = head->next; 109 | /* pred和next会被使用,所以进行标记 */ 110 | hazard_t *hp1 = haz_get(0); 111 | hazard_t *hp2 = haz_get(1); 112 | while (item) { 113 | node_t *sitem = STRIP_MARK(item); 114 | haz_set_ptr(hp1, STRIP_MARK(pred)); 115 | haz_set_ptr(hp2, STRIP_MARK(item)); 116 | /* 117 | 如果已被标记,那么紧接着item可能被移除链表甚至释放,所以需要重头查找 118 | */ 119 | if (HAS_MARK(sitem->next)) { 120 | trace("item->next %p %p marked, retry\n", item, sitem->next); 121 | return l_find(pred_ptr, item_ptr, head, key); 122 | } 123 | int d = KEY_CMP(sitem->key, key); 124 | if (d >= 0) { 125 | trace("item %p match key %d, pred %p\n", item, key, pred); 126 | *pred_ptr = pred; 127 | *item_ptr = item; 128 | return d == 0 ? TRUE : FALSE; 129 | } 130 | pred = item; 131 | item = sitem->next; 132 | } 133 | trace("not found key %d\n", key); 134 | *pred_ptr = pred; 135 | *item_ptr = NULL; 136 | return FALSE; 137 | } 138 | 139 | int l_exist(node_t *head, key_t key) { 140 | node_t *pred, *item; 141 | return l_find(&pred, &item, head, key); 142 | } 143 | 144 | int l_count(node_t *head) { 145 | int cnt = 0; 146 | node_t *item = STRIP_MARK(head->next); 147 | while (item) { 148 | if (!HAS_MARK(item->next)) { 149 | cnt ++; 150 | } 151 | item = STRIP_MARK(item->next); 152 | } 153 | return cnt; 154 | } 155 | 156 | value_t l_lookup(node_t *head, key_t key) { 157 | node_t *pred, *item; 158 | if (l_find(&pred, &item, head, key)) { 159 | return STRIP_MARK(item)->val; 160 | } 161 | return NULL_VALUE; 162 | } 163 | -------------------------------------------------------------------------------- /l_list.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lock-free order-list sample implementation based on 3 | * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf 4 | * Kevin Lynx 2015.05.02 5 | */ 6 | 7 | #ifndef __l_list_h 8 | #define __l_list_h 9 | 10 | #include "common.h" 11 | 12 | #ifdef __cplusplus 13 | extern "C" { 14 | #endif 15 | 16 | /* test purpose */ 17 | typedef int key_t; 18 | typedef int value_t; 19 | #define NULL_VALUE 0 20 | 21 | #define KEY_CMP(k1, k2) (k1 - k2) 22 | 23 | typedef struct node_t { 24 | key_t key; 25 | value_t val; 26 | struct node_t *next; 27 | } node_t; 28 | 29 | node_t *l_alloc(); 30 | void l_free(node_t *head); 31 | int l_insert(node_t *head, key_t key, value_t val); 32 | int l_remove(node_t *head, key_t key); 33 | int l_count(node_t *head); 34 | int l_exist(node_t *head, key_t key); 35 | value_t l_lookup(node_t *head, key_t key); 36 | 37 | #ifdef __cplusplus 38 | } 39 | #endif 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /l_test.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lock-free order-list sample implementation based on 3 | * http://www.research.ibm.com/people/m/michael/spaa-2002.pdf 4 | * Kevin Lynx 2015.05.02 5 | */ 6 | 7 | #include "l_list.h" 8 | #include "common.h" 9 | #include "haz_ptr.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define ASSERT assert 17 | 18 | int _wait = 0; 19 | int _tcount = 10; 20 | int _ttimes = 1000; 21 | 22 | void *worker(void *u) { 23 | node_t *list = (node_t*) u; 24 | (void)ATOMIC_ADD(&_wait, -1); 25 | unsigned int seed = (unsigned int) time(NULL); 26 | int max = _ttimes; 27 | do {} while (_wait > 0); 28 | for (int i = 1; i <= _ttimes; ++i) { 29 | int r = rand_r(&seed) % max; 30 | if (r % 2) { 31 | l_insert(list, r, i); 32 | } else { 33 | l_remove(list, r); 34 | } 35 | } 36 | printf("list size: %d\n", l_count(list)); 37 | haz_gc(); 38 | return NULL; 39 | } 40 | 41 | int main(int argc, char **argv) { 42 | { 43 | node_t *list = l_alloc(); 44 | ASSERT(l_insert(list, 1, 1) == 1); 45 | ASSERT(l_insert(list, 2, 2) == 2); 46 | ASSERT(l_insert(list, 2, 3) == 2); 47 | ASSERT(l_lookup(list, 1) == 1); 48 | ASSERT(l_lookup(list, 2) == 3); 49 | ASSERT(l_remove(list, 3) == FALSE); 50 | ASSERT(l_remove(list, 1) == TRUE); 51 | ASSERT(l_lookup(list, 1) == NULL_VALUE); 52 | ASSERT(l_insert(list, 1, 2) == 2); 53 | l_free(list); 54 | } 55 | { 56 | node_t *list = l_alloc(); 57 | if (argc > 1) 58 | _tcount = atoi(argv[1]); 59 | if (argc > 2) 60 | _ttimes = atoi(argv[2]); 61 | pthread_t *thread = (pthread_t*) malloc(sizeof(pthread_t*) * _tcount); 62 | _wait = _tcount; 63 | printf("thread count %d, loop count %d\n", _tcount, _ttimes); 64 | for (int i = 0; i < _tcount; ++i) { 65 | pthread_create(&thread[i], NULL, worker, list); 66 | } 67 | for (int i = 0; i < _tcount; ++i) { 68 | pthread_join(thread[i], NULL); 69 | } 70 | l_free(list); 71 | free(thread); /* */ 72 | } 73 | return 0; 74 | } 75 | --------------------------------------------------------------------------------