注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。
一、无锁队列是什么?
有锁队列和无锁队列
有锁队列通过互斥锁或其他同步机制保证线程安全的队列。
无锁队列通过原子操作来实现线程安全的队列,属于非阻塞队列。
二、为什么需要无锁队列
锁的局限:
线程阻塞带来的切换
死锁风险
性能瓶颈,高并发下锁竞争激烈,吞吐量下降
三、无锁、无等待区分
lock-free(无锁)和 wait-free(无等待)的区别
进度:
无锁至少一个线程成功,其他可能重试
无等待所有线程必成功,无重试
实现:
无锁依赖 cas 等原子操作
无等待 exchange 等原子操作
四、无锁的队列的种类
spsc、mpsc、spmc、mpmc
s:single
p:producer
m:multiple
c:consumer
五、无锁队列的设计:
- 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)
};
- 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__