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__
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习