Lock-free-Stack 算法探讨

2023-05-17 20:10:09 +08:00
 Reiouf

这几天看了许多关于 lock-free 和 memory-reordering 的资料,在看到 C++ Concurrency in Action 时候写了一个自己理解的 lock-free 结构。


class LockFreeStack
{
    using T = int;

private:
    struct Node
    {
        T val;
        Node *next;
    };

public:
    LockFreeStack() : head(nullptr), length(0){};

    void Push(const T &val)
    {
        Node *new_node = new Node{val, nullptr};
        Node *old_head = nullptr;
        do
        {
            old_head = head.load(std::memory_order_release);
        } while (!head.compare_exchange_strong(old_head, new_node, std::memory_order_acquire, std::memory_order_acquire));
        new_node->next = old_head;
        length.fetch_add(1, std::memory_order_relaxed);
    }
};

但是和书上不一致:

class lock_free_stack
{
private:
    struct node
    {
        std::shared_ptr<T> data;
        node* next;
        node(T const& data_):
            data(std::make_shared<T>(data_))
        {}
    };
    std::atomic<node*> head;
public:
    void push(T const& data)
    {
        node* const new_node=new node(data); // 1
        new_node->next=head.load(); //2 
        while(!head.compare_exchange_weak(new_node->next,new_node)); //3
    } 
};

我就很疑惑,他算法中一个线程 A 如果执行到 @2 被休眠,如果其他 push 线程 直接执行 @2 ,@3 ,修改了 head atomic 值,线程 A 不就一直卡死了吗?

1777 次点击
所在节点    程序员
14 条回复
Reiouf
2023-05-17 20:21:39 +08:00
怎么没人捏
Reiouf
2023-05-17 20:24:11 +08:00
我看了 http://15418.courses.cs.cmu.edu/spring2013/article/46 cmu 的一个作业也是在 while 套 cas 如此解决的。
piaodazhu
2023-05-17 20:38:47 +08:00
第一个在倒数第二三行之间有可能出现暂时的断链情况?
第二个我也感觉有问题。
你发那个链接里第一个代码倒是可以理解的。
Reiouf
2023-05-17 21:00:12 +08:00
@piaodazhu 你说的是` new_node->next = old_head;` 在执行之前就被 睡掉了的情况吗?确实可能诶
那我把 new_node->next = old_head; 放到 while 里就完美了
你觉得呢
C47CH
2023-05-17 21:17:22 +08:00
去看了下 compare_exchange_weak 的定义,如果不相等的话会更新 new_node->next 的值,然后继续比较,最后会完成修改。
DeltaC
2023-05-17 21:44:34 +08:00
打开 Cppreference 的 compare_exchange 正巧就是你这个算法的这个问题。
看了下注释,这和编译器版本有关。

> https://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange
exch4nge
2023-05-17 22:03:25 +08:00
贴的第一个代码不对,贴的第二个跟链接里的代码是对的。原因上面 @piaodazhu @C47CH 说了,虽然重复我也说一下。

贴的第一个:
- head 已经在 compare exchange 修改了,不过 new_node 的 next 没设置,导致断了
- 用 compare_exchange_weak 就好,不过我不确定你用的 memory order 对不对
- 这里 length 更新会比实际慢,所以会有差异
- while 里的 old_head = head.load(std::memory_order_release); 除了第一次有用外,没什么用,应该放在 do 上面

贴的第二个,解答你的问题。
如果线程 A 如果执行到 @2 被休眠,如果其他 push 线程 直接执行 @2 ,@3 修改了 head atomic 值,线程 A 不就一直卡死了吗?
不会卡死,因为 compare_exchange_weak 会失败,同时会设置 new_node->next 的值。所以是对的。

4L 你的问题:new_node->next = old_head 放到 while 循环里后逻辑是对的。其它问题参考上面的问题列表
artnowben
2023-05-17 22:11:05 +08:00
lockfree 队列比较实用,DPDK 里面的实现性能非常高,有官方文档介绍。
piaodazhu
2023-05-18 08:59:50 +08:00
学习了! compare_exchange_weak 和 compare_exchange_strong 还是很不一样的
Reiouf
2023-05-18 11:01:11 +08:00
@piaodazhu 我重新看了 cppdocs ,compare_exchange_strong 会在失败的时候置换 expected 值的,他两的差异点在于 week 可能 fail spuriously.
Reiouf
2023-05-18 11:13:48 +08:00
@artnowben 害 我先看看书把
artnowben
2023-05-18 11:21:27 +08:00
Reiouf
2023-05-18 16:07:48 +08:00
C++ concurrency in Actionn 内容真太丰富了,后面看的速度降了好多
Reiouf
2023-05-18 16:11:52 +08:00
@artnowben get 不到他们的点

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/940789

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX