多线程-无锁队列入队的实现

76 阅读2分钟

节点结构:

template<typename T>
struct Node {
    T data;
    std::atomic<std::shared_ptr<Node<T>>> next; 
    Node(const T& val) : data(val), next(nullptr) {}
    Node() : data(T{}), next(nullptr) {}
};

使用哨兵节点作为头节点,方便插入和删除的操作保持一致


template<typename T>
class LockFreeQueue {
private:
    // 使用原子共享指针来管理节点,自动处理内存回收,避免ABA和UAF问题
    std::atomic<std::shared_ptr<Node<T>>> head;
    std::atomic<std::shared_ptr<Node<T>>> tail;

public:

    LockFreeQueue() {
        auto dummyNode = std::make_shared<Node>();
        head.store(dummyNode);
        tail.store(dummyNode);
    }
    

enqueue:入队操作

new_node = make_shared<Node<T>>(val);  // (1)
tail->next = new_node;                 // (2)
tail = new_node;                       // (3)

注意:只有当(2)成立了才会去执行(3)

那么思考多线程情况下应该如何去操作?

迭代版本1:

auto newNode = std::make_shared<Node<T>> val;

// 先获得:tail->next
auto oldTail = tail.load();
auto next = oldTail->next;
while(true){
    //现在尝试去执行:tail->next = new_node;
    if(oldTail->next.compare_exchange_weak(next,newNode)){
        //(2)成功了,那么去执行(3).

        // 此外,此时可能发生一件事:我把节点插入进去了,我还没修改tail,我的节点就切换了
        // 此时产生的影响:当前语句的执行,以及auto next = oldTail->next;,我的next可能不是null了
        // 去看迭代版本2

        if(tail.compare_exchange_weak(oldTail,newNode)){
            return;
        }   
    }

}

迭代版本2:


auto newNode = std::make_shared<Node<T>> val;
while(true){
    auto oldTail = tail.load();
    auto next = oldTail->next;

    if(next==null){
        if(oldTail->next.compare_exchange_weak(next,newNode)){
            if(tail.compare_exchange_weak(oldTail,newNode)){
                return;
            }
        }
    }else{
        // next 不为空,说明有其他线程完成了链接但没移动 tail 指针
        // 我们把 tail 指针向后移动
        tail.compare_exchange_strong(oldTail, next);
    }
}
  • 致命错误

    • T1:线程插入节点,但是没有修改tail
    • T2:发现T1没有修改tail,那么就去尝试修改tail
    • T1:返回用户,发现我的tail已经被别人修改了,我不需要修改,但是并没有直接return,而且继续尝试while继续插入节点newNode。导致线程T1出问题了。

那么也就是,我无论tail.compare_exchange_weak(oldTail,newNode)执行是否成功其实都说明一件事,当前节点插入成功,无需额外处理了。

迭代版本3:


auto newNode = std::make_shared<Node<T>> val;
while(true){
    auto oldTail = tail.load();
    auto next = oldTail->next;

    if(next==null){
        if(oldTail->next.compare_exchange_weak(next,newNode)){
            tail.compare_exchange_weak(oldTail,newNode))
            return;
        }
    }else{
        // next 不为空,说明有其他线程完成了链接但没移动 tail 指针
        // 我们把 tail 指针向后移动
        tail.compare_exchange_strong(oldTail, next);
    }
}
  • 代码正确

迭代版本4:


void enqueue(const T& val) {
    auto newNode = std::make_shared<Node>(val);
    for (;;) {
        auto oldTail = tail.load();
        auto next = oldTail->next;

        if (oldTail == tail.load()) {
            if (next == nullptr) {
                if (oldTail->next.compare_exchange_weak(next, newNode)) {
                    tail.compare_exchange_strong(oldTail, newNode);
                    return;
                }
            } else {
                tail.compare_exchange_strong(oldTail, next);
            }
        }
    }
}
  • 正确:
    • if (oldTail == tail.load()) 可以用来判断 auto oldTail = tail.load(); auto next = oldTail->next;直接是不是出现了问题。