真实示例,其中 std::atomic::compare_exchange 与两个 memory_order 参数一起使用

2024-03-06

你能给出一个真实的例子,其中两个 memory_order 参数版本std::atomic::compare_exchange使用是有原因的(因此一个 memory_order 参数版本不够)?


在许多情况下,第二个内存排序参数compare_exchange被设定为memory_order_relaxed。在这些情况下,省略它通常并没有错,只是效率可能较低。

这是一个简单的、无锁的列表/堆栈的示例,它需要第二个不同的排序参数compare_exchange_weak为了避免数据竞争。

致电push可以并发执行,但为了避免无锁数据操作的复杂性, 假设在调用时不能从堆栈中删除节点push被处决;即避免悬空指针。

template<typename T>
class mystack {

    struct node {
        node *next = nullptr;

        T data;
        int id;

        node(int id) : id{id} { }
    };

    std::atomic<node *> head{nullptr};

public:
    void push(T data, int id);
    bool pop(T &data); // not implemented
};


template<typename T>
void mystack<T>::push(T data, int id)
{
    node *newnode = new node{id};

    newnode->data = std::move(data);

    node *current_head = head.load(std::memory_order_relaxed);   // A

    for (;;)
    {
        newnode->next = current_head;

        if (head.compare_exchange_weak(current_head, newnode,
                                       std::memory_order_release,   // B
                                       std::memory_order_acquire))  // C
        {
            /*
             * 'current_head' may not be derefenced here since the initial load (at A)
             * does not order memory 'current_head' is pointing at.
             *
             * a release barrier (at B) is necessary to make 'newnode' available
             * to other threads
             */
            std::cout << "Insertion successful\n";

            break;

        } else
        {
            /*
             * 'current_head' is the updated head pointer after 'compare_exchange' failed
             * Since it was inserted by another thread (the CAS failed),
             * an acquire barrier must be set (at C) in order to be able to access data
             * 'current_head' is pointing at.
             */
            std::cout << "Insertion failed after head changed to id: " <<
                          current_head->id << std::endl;
        }
    }
}

In push, 最初的load(A处)是一个宽松的操作,这意味着即使head指针被原子加载, 它可能不会被取消引用,因为它引用的内存在此线程中是无序的。

In case compare_exchange_weak返回成功,newnode插入到列表的头部,并通过设置释放屏障(在 B 处)可供其他线程使用。 另一个访问此数据的线程(稍后,通过pop)需要设置获取障碍。

In case compare_exchange_weak返回失败(虚假地忘记),另一个线程刚刚插入了一个新的node实例和current_head更新为新值head. Since current_head现在指向在另一个线程中分配和释放的数据,如果出现以下情况,则需要获取屏障current_head将被取消引用。
这是事实,因为cout失败消息包括current_head->id.

如果省略最后一个参数,则第一个屏障参数将用于失败load场景,但由于这是一个释放障碍, 有效屏障会衰减到memory_order_relaxed,导致数据争用current_head->id.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

真实示例,其中 std::atomic::compare_exchange 与两个 memory_order 参数一起使用 的相关文章

随机推荐