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__
相关推荐
saoys2 小时前
Opencv 学习笔记:创建与原图等尺寸的空白图像
笔记·opencv·学习
晓幂8 小时前
【2025】HECTF
笔记·学习·web安全
慕云紫英9 小时前
基金申报的一点经验
学习·aigc
微露清风9 小时前
系统性学习C++-第十八讲-封装红黑树实现myset与mymap
java·c++·学习
宝贝儿好9 小时前
【强化学习】第六章:无模型控制:在轨MC控制、在轨时序差分学习(Sarsa)、离轨学习(Q-learning)
人工智能·python·深度学习·学习·机器学习·机器人
大、男人9 小时前
python之asynccontextmanager学习
开发语言·python·学习
做cv的小昊9 小时前
【TJU】信息检索与分析课程笔记和练习(8)(9)发现系统和全文获取、专利与知识产权基本知识
大数据·笔记·学习·全文检索·信息检索
盐焗西兰花10 小时前
鸿蒙学习实战之路-蓝牙设置完全指南
学习·华为·harmonyos
hkNaruto10 小时前
【AI】AI学习笔记:MCP协议与gRPC、OpenAPI的差异
人工智能·笔记·学习
笨鸟笃行10 小时前
0基础小白使用ai能力将本地跑的小应用上云(作为个人记录)
人工智能·学习