Linux C/C++ 学习日记(55):原子操作(四):实现无锁队列

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、无锁队列是什么?

有锁队列和无锁队列

  1. 有锁队列通过互斥锁或其他同步机制保证线程安全的队列。

  2. 无锁队列通过原子操作来实现线程安全的队列,属于非阻塞队列。

二、为什么需要无锁队列

锁的局限:

  1. 线程阻塞带来的切换

  2. 死锁风险

  3. 性能瓶颈,高并发下锁竞争激烈,吞吐量下降

三、无锁、无等待区分

lock-free(无锁)和 wait-free(无等待)的区别

进度:

  1. 无锁至少一个线程成功,其他可能重试

  2. 无等待所有线程必成功,无重试

实现:

  1. 无锁依赖 cas 等原子操作

  2. 无等待 exchange 等原子操作

四、无锁的队列的种类

spsc、mpsc、spmc、mpmc

s:single

p:producer

m:multiple

c:consumer

五、无锁队列的设计:

  1. spsc

ringbuffer:环形队列:定长

  • wait-free: 无等待, 单线程操作必定成功
  • head = tail 空 , head + 1 = tail 满。
  • 读:判断 tail == head, 未空则读取tail, 然后 tail + 1 。
  • 写: 判断 head+1 == tail, 未满则向head写数据, 然后head++
  • 这里用read == tail, write == head
cpp 复制代码
#pragma once

// SPSC
#include <atomic>
#include <cstddef>
#include <type_traits>


// wait-free: 无等待, 单线程操作必定成功
// head = tail 空 , head + 1 = tail 满。  读:判断 tail == head, 未空则读取tail, 然后 tail + 1 。 写: 判断 head+1 == tail, 未满则向head写数据, 然后head++
// 这里用read == tail, write == head
template<typename T, std::size_t Capacity>
class RingBuffer // 无等待
{
public:
    static_assert(Capacity && !(Capacity & (Capacity - 1)), "Capacity must be power of 2"); // 静态断言:在编译器不满足就会报错
    RingBuffer() : read_(0), write_(0) {}

    ~RingBuffer() {
        std::size_t r = read_.load(std::memory_order_relaxed);
        std::size_t w = write_.load(std::memory_order_relaxed);
        while (r != w) {
            reinterpret_cast<T *>(&buffer_[r])->~T();
            r = (r + 1) & (Capacity - 1);
        }
    }

    // 这里使用万能引用和完美转发,支持左值和右值
    template<typename U>
    bool Push(U && value) {
        const std::size_t w = write_.load(std::memory_order_relaxed);
        const std::size_t next_w = (w + 1) & (Capacity - 1);
        // 检查缓冲区是否满
        if (next_w == read_.load(std::memory_order_acquire)) {
            return false;
        }
        new (&buffer_[w]) T(std::forward<U>(value));
        write_.store(next_w, std::memory_order_release);
        return true;
    }

    bool Pop(T & value) {
        const std::size_t r = read_.load(std::memory_order_relaxed);
        // 检查缓冲区是否空
        if (r == write_.load(std::memory_order_acquire)) {
            return false;
        }

        // 取出元素并析构
        value = std::move(*reinterpret_cast<T *>(&buffer_[r]));
        reinterpret_cast<T *>(&buffer_[r])->~T();
        read_.store((r + 1) & (Capacity - 1), std::memory_order_release);
        return true;
    }

    std::size_t Size() const {
        const std::size_t r = read_.load(std::memory_order_acquire);
        const std::size_t w = write_.load(std::memory_order_acquire);
        return (w >= r) ? (w - r) : (Capacity - r + w);
    }


private:
//cache line  64B
    alignas(64) std::atomic<std::size_t> read_;
    alignas(64) std::atomic<std::size_t> write_;
    alignas(64) std::aligned_storage_t<sizeof(T), alignof(T)> buffer_[Capacity];  // 支持 pod 和 非 pod 类型 (placement new)
};
  1. mpsc
  • wait-free
  • 以单链表的形式,带头节点(不保存数据), 初始:head = tail = 头节点。
  • 插入: 将节点插入head->next, 然后head = head->next。
  • 读取: 判断tail->next 非空, 读取tail->next的数据,然后tail = tail -> next
cpp 复制代码
#ifndef _MARK_MPSC_QUEUE_H
#define _MARK_MPSC_QUEUE_H

#include <atomic>
#include <utility>


// 以单链表的形式,带头节点(不保存数据), 初始:head = tail = 头节点。 插入: 将节点插入head->next, 然后head = head->next。 读取: 判断tail->next 非空, 读取tail->next的数据,然后tail = tail -> next
template<typename T>
class MPSCQueueNonIntrusive
{
public:
    MPSCQueueNonIntrusive() : _head(new Node()), _tail(_head.load(std::memory_order_relaxed))
    {
        Node* front = _head.load(std::memory_order_relaxed);
        front->Next.store(nullptr, std::memory_order_relaxed);
    }

    ~MPSCQueueNonIntrusive()
    {
        T* output;
        while (Dequeue(output))
            delete output;

        Node* front = _head.load(std::memory_order_relaxed);
        delete front;
    }

    void Enqueue(T* input)
    {
        Node* node = new Node(input);
        Node* prevHead = _head.exchange(node, std::memory_order_acq_rel);
        prevHead->Next.store(node, std::memory_order_release);
    }

    bool Dequeue(T*& result)
    {
        Node* tail = _tail.load(std::memory_order_relaxed);
        Node* next = tail->Next.load(std::memory_order_acquire);
        if (!next)
            return false;

        result = next->Data;
        _tail.store(next, std::memory_order_release);
        delete tail;
        return true;
    }

private:
    struct Node
    {
        Node() = default;
        explicit Node(T* data) : Data(data)
        {
            Next.store(nullptr, std::memory_order_relaxed);
        }

        T* Data;
        std::atomic<Node*> Next;
    };

    std::atomic<Node*> _head;
    std::atomic<Node*> _tail;

    MPSCQueueNonIntrusive(MPSCQueueNonIntrusive const&) = delete;
    MPSCQueueNonIntrusive& operator=(MPSCQueueNonIntrusive const&) = delete;
};

template<typename T, std::atomic<T*> T::* IntrusiveLink>
class MPSCQueueIntrusive
{
public:
    MPSCQueueIntrusive() : _dummyPtr(reinterpret_cast<T*>(std::addressof(_dummy))), _head(_dummyPtr), _tail(_dummyPtr)
    {
        // _dummy is constructed from aligned_storage and is intentionally left uninitialized (it might not be default constructible)
        // so we init only its IntrusiveLink here
        std::atomic<T*>* dummyNext = new (&(_dummyPtr->*IntrusiveLink)) std::atomic<T*>();
        dummyNext->store(nullptr, std::memory_order_relaxed);
    }

    ~MPSCQueueIntrusive()
    {
        T* output;
        while (Dequeue(output))
            delete output;
    }

    void Enqueue(T* input)
    {
        (input->*IntrusiveLink).store(nullptr, std::memory_order_release);
        T* prevHead = _head.exchange(input, std::memory_order_acq_rel);
        (prevHead->*IntrusiveLink).store(input, std::memory_order_release);
    }

    bool Dequeue(T*& result)
    {
        T* tail = _tail.load(std::memory_order_relaxed);
        T* next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
        if (tail == _dummyPtr)
        {
            if (!next)
                return false;

            _tail.store(next, std::memory_order_release);
            tail = next;
            next = (next->*IntrusiveLink).load(std::memory_order_acquire);
        }

        if (next)
        {
            _tail.store(next, std::memory_order_release);
            result = tail;
            return true;
        }

        T* head = _head.load(std::memory_order_acquire);
        if (tail != head)
            return false;

        Enqueue(_dummyPtr);
        next = (tail->*IntrusiveLink).load(std::memory_order_acquire);
        if (next)
        {
            _tail.store(next, std::memory_order_release);
            result = tail;
            return true;
        }
        return false;
    }

private:
    std::aligned_storage_t<sizeof(T), alignof(T)> _dummy;
    T* _dummyPtr;
    std::atomic<T*> _head;
    std::atomic<T*> _tail;

    MPSCQueueIntrusive(MPSCQueueIntrusive const&) = delete;
    MPSCQueueIntrusive& operator=(MPSCQueueIntrusive const&) = delete;
};

template<typename T, std::atomic<T*> T::* IntrusiveLink = nullptr>
using MPSCQueue = std::conditional_t<IntrusiveLink != nullptr, MPSCQueueIntrusive<T, IntrusiveLink>, MPSCQueueNonIntrusive<T>>;

#endif // MPSCQueue_h__
相关推荐
小韩博3 小时前
小迪安全 · 第 39 课学习笔记
笔记·学习·安全·网络安全
非凡ghost3 小时前
Topaz Video(人工智能视频增强软件)
人工智能·windows·学习·音视频·软件需求
重生之我在番茄自学网安拯救世界4 小时前
网络安全中级阶段学习笔记(十):upload靶场实战(17关以及问题解决)
笔记·学习·网络安全·文件上传漏洞·图片木马
炽烈小老头12 小时前
【每天学习一点算法 2025/12/19】二叉树的层序遍历
数据结构·学习·算法
xian_wwq12 小时前
【学习笔记】数据血缘
笔记·学习·数据血缘
map_vis_3d13 小时前
JSAPIThree LODModel 性能优化学习笔记:细节层次模型加载
笔记·学习·3d
MarkHD13 小时前
智能体在车联网中的应用:第9天 核心工具链与仿真世界:SUMO交通仿真入门——从安装到构建你的第一个虚拟十字路口
学习
lxh011313 小时前
2025/12/18 学习总结
学习
im_AMBER14 小时前
数据结构 13 图 | 哈希表 | 树
数据结构·笔记·学习·算法·散列表