Is std::atomic_compare_exchange_weak thread-unsafe by design?

后端 未结 5 1739
北海茫月
北海茫月 2020-12-28 17:47

It was brought up on cppreference atomic_compare_exchange Talk page that the existing implementations of std::atomic_compare_exchange_weak compute the boolea

5条回答
  •  野趣味
    野趣味 (楼主)
    2020-12-28 18:41

    Quoting Duncan Forster from the linked page:

    The important thing to remember is that the hardware implementation of CAS only returns 1 value (the old value) not two (old plus boolean)

    So there's one instruction - the (atomic) CAS - which actually operates on memory, and then another instruction to convert the (atomically-assigned) result into the expected boolean.

    Since the value in %rax was set atomically and can't then be affected by another thread, there is no race here.

    The quote is anyway false, since ZF is also set depending on the CAS result (ie, it does return both the old value and the boolean). The fact the flag isn't used might be a missed optimisation, or the cmpq might be faster, but it doesn't affect correctness.


    For reference, consider decomposing compare_exchange_weak like this pseudocode:

    T compare_exchange_weak_value(atomic *obj, T *expected, T desired) {
        // setup ...
        lock cmpxchgq   %rcx, (%rsp) // actual CAS
        return %rax; // actual destination value
    }
    
    bool compare_exchange_weak_bool(atomic *obj, T *expected, T desired) {
        // CAS is atomic
        T actual = compare_exchange_weak_value(obj, expected, desired);
        // now we figure out if it worked
        return actual == *expected;
    }
    

    Do you agree the CAS is properly atomic?


    If the unconditional store to expected is really what you wanted to ask about (instead of the perfectly safe comparison), I agree with Sebastian that it's a bug.

    For reference, you can work around it by forcing the unconditional store into a local, and making the potentially-visible store conditional again:

    struct node {
      int data;
      node* next;
    };
    
    std::atomic head;
    
    void push(int data) {
      node* new_node = new node{data};
      node* cur_head = head.load(std::memory_order_relaxed);
      do {
        new_node->next = cur_head;
      } while (!head.compare_exchange_weak(cur_head, new_node,
                std::memory_order_release, std::memory_order_relaxed));
    }
    

提交回复
热议问题