The realization of the chain list without lock order

Source: Internet
Author: User

The lock-free ordered list guarantees the uniqueness of the element, making it available to the bucket of the hash table, or even directly as a less efficient map. The lock-free implementation of the normal list is relatively simple because the insertion element can be inserted in the header, and the insertion of the ordered list is arbitrary.

This paper is based on the thesis high performance Dynamic Lock-free Hash tables implementation.

Problems

The main operation of the list contains insert and remove , first a simple implementation of a version, you will see the problem, the following code is used only as an example:

struct node_t {key_t key;        value_t Val;    node_t *next;    };        int L_find (node_t **pred_ptr, node_t **item_ptr, node_t *head, key_t key) {node_t *pred = head;        node_t *item = head->next;            while (item) {int d = key_cmp (Item->key, KEY);                if (d >= 0) {*pred_ptr = pred;                *item_ptr = Item; return d = = 0?            True:false;            } pred = Item;        item = item->next;        } *pred_ptr = pred;        *item_ptr = NULL;    return FALSE;        } int L_insert (node_t *head, key_t key, value_t val) {node_t *pred, *item, *new_item;            while (TRUE) {if (L_find (&pred, &item, Head, key)) {return FALSE;            } New_item = (node_t*) malloc (sizeof (node_t));            New_item->key = key;            New_item->val = val;            New_item->next = Item;  A. If the pred itself is removed          if (CAS (&pred->next, item, New_item)) {return TRUE;        } free (New_item);        }} int L_remove (node_t *head, key_t key) {node_t *pred, *item;            while (true) {if (!l_find (&pred, &item, Head, key)) {return TRUE; }//B. If pred is removed, if item is also removed if (CAS (&pred->next, item, Item->next)) {Haz_                Free (item);            return TRUE; }        }    }

l_findThe function returns the pre-order element and the element itself, code A and B, although pred it gets the and item , but at CAS the time, it may be removed by other threads. Even in the l_find process, each of its elements may be removed. The problem is that any time you get an element, you're not sure if it's still working . The validity of an element includes whether it is still in the linked list, and whether the memory it points to is still valid.

Solution Solutions

The problem of element validity determination can be solved by adding a validity flag bit to the element pointer and cooperating with the mutex of CAS operation .

Because node_t put in memory will be aligned, so pointing node_t to the pointer value is not used, which can be set in the low number of flags, so in the time of the CAs, the implementation of the DCAs effect, equivalent to two logical operations into an atomic operation. Imagine the thread safety of the reference count object, where the wrapped pointer is thread-safe, but the object itself is not.

The mutex of CAs, when several threads CAs the same object, only one thread succeeds, and the failed thread can determine that the target object has changed. Improved code (code is only used for examples, not guaranteed correctly):

typedef size_t MARKABLE_T; The lowest position 1, indicating that the element was deleted #define HAS_MARK (P) ((markable_t) P & 0x01) #define MARK (P) ((markable_t) p | 0x01) #define ST Rip_mark (P) ((markable_t) P & ~0x01) int L_insert (node_t *head, key_t key, value_t val) {node_t *pred, *item        , *new_item;            while (TRUE) {if (L_find (&pred, &item, Head, key)) {return FALSE;            } New_item = (node_t*) malloc (sizeof (node_t));            New_item->key = key;            New_item->val = val;            New_item->next = Item;            A. Although find has a valid pred, pred may be deleted before the following code, Pred->next is marked//pred->next! = Item, the CAS will fail and retry after failure            if (CAS (&pred->next, item, New_item)) {return TRUE;        } free (New_item);    } return FALSE;        } int L_remove (node_t *head, key_t key) {node_t *pred, *item; while (TRUE) {if (!l_find (&pred, &item, Hea(d, key)) {return FALSE;            } node_t *inext = item->next; B. Mark Item->next before deleting item, if the CAS fails, then the same as insert, there are other threads after the Find//deleted item, failed to retry if (!).            CAS (&item->next, Inext, MARK (Inext))) {continue;                }//C. On the same element when the item is deleted, only one thread will successfully go here if (CAS (&pred->next, Item, Strip_mark (Item->next))) {                Haz_defer_free (item);            return TRUE;    }} return FALSE;        } int L_find (node_t **pred_ptr, node_t **item_ptr, node_t *head, key_t key) {node_t *pred = head;        node_t *item = head->next;        hazard_t *hp1 = haz_get (0);        hazard_t *HP2 = haz_get (1);            while (item) {haz_set_ptr (HP1, pred);            Haz_set_ptr (HP2, item);                 /* If it is already marked, then the item may be removed from the list or even released, so it needs to be looked up */if (Has_mark (Item->next)) { Return L_find (pred_ptr, Item_ptr, head, key);            } int d = key_cmp (Item->key, KEY);                if (d >= 0) {*pred_ptr = pred;                *item_ptr = Item; return d = = 0?            True:false;            } pred = Item;        item = item->next;        } *pred_ptr = pred;        *item_ptr = NULL;    return FALSE; }

Functions such as

haz_get , haz_set_ptr are a hazard pointer implementation that supports GC for memory under multiple threads. In the preceding code, when you delete an element item , item->next is marked, which makes the CAS in insert There is no need to make any adjustments. Summarize the thread race situation here:

    • insertIn the find normal and, and pred item pred->next == item then in the CAS previous thread removed pred , at this time pred->next == MARK(item) , CAS failed, retry; the deletion is divided into 2 cases: a) removed from the list, tagged, pred can continue to access; b) pred Memory may be freed and then used pred incorrectly. In order to deal with the situation B, so the introduction of similar hazard pointer mechanism, can effectively protect any one pointer p as long as the thread is using it, its memory will not be really released
    • insertThere are multiple threads in the pred post-insertion element, and at this point the same is guaranteed by the insert medium, which CAS does not say much
    • removeIn the case of the same insert , find got the valid pred and next , but at CAS the time pred by the other thread deleted, at this time the situation is the same insert , CAS failed, retry
    • Whenever you change the structure of a linked list, you remove insert need to retry the operation.
    • find, you may encounter a marked delete, the item item remove implementation is likely to be deleted, so you need to start the traversal
ABA Issues

The problem of ABA is still there, insert in:

if (CAS (&pred->next, item, New_item)) {        return TRUE;    }

If the CAS previous, pred after item is removed, and the same address value is added, but its value is changed, this time CAS will succeed, but the list may not be ordered.pred->val < new_item->val > item->val

To solve this problem, you can use the pointer value address to align the other bits to store a count that represents pred->next the number of changes. When insert it pred is taken, the pred->next stored count is assumed to be 0, CAS before other threads pred->next have been removed and added back item , and the pred->next count in this case increases, resulting insert in a CAS failure.

The lowest bit remains as a delete flag    #define MASK ((sizeof (node_t)-1) & ~0x01)    #define GET_TAG (P) ((markable_t) P & MASK)    #d Efine tag (P, Tag) ((markable_t) p | (tag))    #define MARK (P) ((markable_t) p | 0x01)    #define Has_mark (P) ((markable_t) P & 0x01)    #define Strip_mark (P) (( node_t*) ((markable_t) P & ~ (MASK | 0x01))

removeImplementation of:

/* Mark before Delete *    /if (! CAS (&sitem->next, Inext, MARK (Inext))) {        continue;    }    int tag = Get_tag (pred->next) + 1;    if (CAS (&pred->next, item, Tag (Strip_mark (sitem->next), tag))) {        haz_defer_free (sitem);        return TRUE;    }

insertThe count can also be updated in pred->next the.

Summarize

A lock-free implementation is inherently dependent on CAS the mutex. By implementing a lock free data structure from scratch, you can feel the tricky of the lock. The final code can be obtained from GitHub here. In order to be simple, the code implements a hazard pointer that is not very powerful and can refer to the previous blog post.

Original address: http://codemacro.com/2015/05/05/lock_free_list/
Written by Kevin Lynx posted athttp://codemacro.com

The realization of the chain list without lock order

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.