0Pricing
C++ Academy · 课时

无锁队列实现

逐步学习单生产者单消费者无锁队列的设计

无锁队列实现 是 CoddyKit 上的免费 C++ Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C++ Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C++ Academy 课程共包含 4 节课。

为什么需要无锁队列?

在高竞争情况下,带互斥锁的队列可能成为瓶颈。无锁队列允许生产者和消费者并发取得进展。

SPSC 与 MPMC

两种类型:

  • SPSC — 单生产者、单消费者(最简单、最快)
  • MPMC — 多生产者、多消费者(最通用)

当您同时控制两端时,SPSC 是自然的选择。

SPSC 环形缓冲区示意图

一个包含两个索引的环形缓冲区:head(消费者)和 tail(生产者)。每一方只更新自己的索引。

template <typename T, size_t N>
class SpscQueue {
    T buffer_[N];
    std::atomic<size_t> head_{0};
    std::atomic<size_t> tail_{0};
public:
    bool push(const T& v);
    bool pop(T& v);
};

SPSC 推入

生产者检查空闲槽位,写入数据,然后通过更新 tail 发布数据。

bool push(const T& v) {
    const size_t t = tail_.load(std::memory_order_relaxed);
    const size_t next = (t + 1) % N;
    if (next == head_.load(std::memory_order_acquire))
        return false;     // full
    buffer_[t] = v;
    tail_.store(next, std::memory_order_release);
    return true;
}

SPSC 弹出

消费者检查是否有数据,读取数据,然后通过更新 head 发布操作完成。

bool pop(T& v) {
    const size_t h = head_.load(std::memory_order_relaxed);
    if (h == tail_.load(std::memory_order_acquire))
        return false;     // empty
    v = buffer_[h];
    head_.store((h + 1) % N, std::memory_order_release);
    return true;
}

内存顺序配对

tail 上的 release-store 会与消费者对 tail 执行的 acquire-load 同步(反之亦然)。如果顺序不正确,数据写入可能会被重排到索引更新之后。

缓存行填充

为避免伪共享,请将 head_ 和 tail_ 放在不同的缓存行上(通常相隔 64 字节)。请使用 alignas。

alignas(64) std::atomic<size_t> head_{0};
alignas(64) std::atomic<size_t> tail_{0};

MPMC:难得多

多个生产者或消费者需要额外的协调,通常要在共享索引上使用 CAS 循环。现有许多设计(Vyukov 队列、MS 队列、基于危险指针的队列)。

Boost.Lockfree

达到生产质量的无锁队列很难实现。请使用 Boost.Lockfree 或 Folly 的 ProducerConsumerQueue,而不要自行实现。

权衡

无锁队列:

  • 在竞争情况下吞吐量更高
  • 延迟有界(无需等待锁)
  • 编写和调试难得多
  • 内存顺序错误不会显现出来,而且难以追踪

测试无锁代码

使用 ThreadSanitizer(-fsanitize=thread)捕获数据竞争。使用插入随机 Sleep 的压力测试来暴露顺序错误。

互斥锁已经足够时

大多数应用不需要无锁队列。请先进行测量——实现良好的互斥锁保护队列通常就能满足性能要求,尤其是在批量处理时。

快速检查

什么是伪共享?为什么要对 head_ 和 tail_ 进行填充?

小结

无锁 SPSC 队列使用环形缓冲区,其中 tail 由生产者拥有,head 由消费者拥有。请使用 acquire/release 顺序,并填充索引,使它们位于不同的缓存行中。对于 MPMC,优先使用经过测试的库。

常见问题解答

「无锁队列实现」课时是免费的吗?

是的 — 「无锁队列实现」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C++ Academy 课程的其余内容,请升级到 CoddyKit PRO。 C++ Academy 课程共包含 4 节课。

「无锁队列实现」这节课中我会学到什么?

逐步学习单生产者单消费者无锁队列的设计 你通过在浏览器中直接运行的动手代码来练习 C++ Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 C++ Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 C++ Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「无锁队列实现」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 C++ Academy 课中编写并运行代码吗?

能。每节 C++ Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. std::atomic 与内存顺序
  2. 比较并交换:CAS 模式
  3. 无锁队列实现
  4. 危险指针与 ABA 问题
← 返回 C++ Academy