无锁队列moodycamel::ConcurrentQueue
概述moodycamel::ConcurrentQueue是由加拿大程序员 Cameron Desrochers 开发的一个高性能、多生产者、多消费者的无锁队列。它以其卓越的性能和丰富的功能而闻名被广泛应用于游戏开发、金融交易、服务器等对性能要求极高的领域。如果你的应用场景中存在大量的生产者-消费者竞争并且对性能有极致要求那么它绝对是一个值得深入研究和使用的优秀工具。对于大多数常规的低并发场景使用std::queue加锁或许就足够了。核心特点无锁使用原子操作而非互斥锁来实现线程同步避免了线程阻塞、上下文切换和死锁问题。多生产者/多消费者完美支持多个线程同时入队和出队。高性能其设计精妙即使在激烈竞争的情况下也能保持出色的吞吐量。宽松的内存序默认使用 C11 内存模型中的memory_order_acquire和memory_order_release在 x86/x64 架构上开销很小。丰富的 API提供了阻塞和非阻塞等多种操作接口。核心设计与原理它的高性能并非来自单一的“魔术”算法而是多种技术结合的结果底层结构它内部是由一个块Block的链表组成的。每个块是一个可以容纳多个元素的数组。这种批量分配的方式减少了内存分配和同步的开销。细粒度锁在生产者端这是它最精妙的设计之一。虽然它对消费者是无锁的但在生产者端它使用了一种每生产者令牌per-producer tokens的机制。每个生产者线程在首次入队时会隐式地或显式地获取一个“令牌”这个令牌关联到内部的一个子队列。效果大部分情况下不同生产者线程操作的是不同的内部子队列从而极大地减少了它们之间的竞争。只有当生产者需要分配新的块时才会有一个全局的轻量级锁。基于块的元素分配生产者会预先分配一个块然后在该块内顺序地填充元素。只有当块被填满后才需要与其他生产者同步以获取新块。消费者从块的头部顺序消费元素。当一个块的所有元素都被消费后该块会被回收并在未来被复用。显式批量操作API 支持enqueue_bulk和try_dequeue_bulk可以一次性入队或出队多个元素这进一步分摊了同步开销极大地提升了吞吐量。基本用法你需要从官方仓库获取代码 https://github.com/cameron314/concurrentqueue无锁队列测试效果http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c#benchmarks1. 简单的入队和出队cpp#include iostream #include thread #include concurrentqueue.h // 包含头文件 moodycamel::ConcurrentQueueint queue; void producer() { for (int i 0; i 10; i) { queue.enqueue(i); // 入队操作 } } void consumer() { int item; for (int i 0; i 10; i) { // try_dequeue 是非阻塞的成功取出返回 true while (!queue.try_dequeue(item)) { // 队列为空可以忙等待、休眠或者做其他工作 std::this_thread::yield(); } std::cout item std::endl; } } int main() { std::thread prod(producer); std::thread cons(consumer); prod.join(); cons.join(); return 0; }2. 使用令牌Token进行优化如果你能预先知道生产者和消费者的数量或者有长期存在的线程使用ProducerToken和ConsumerToken可以获得最佳性能。cppmoodycamel::ConcurrentQueueint queue; void producer_with_token(moodycamel::ProducerToken token) { for (int i 0; i 100; i) { queue.enqueue(token, i); // 使用令牌入队 } } void consumer_with_token(moodycamel::ConsumerToken token) { int item; for (int i 0; i 100; ) { if (queue.try_dequeue(token, item)) { // 使用令牌出队 std::cout item std::endl; i; } else { std::this_thread::yield(); } } } int main() { // 为每个生产者和消费者预先创建令牌 moodycamel::ProducerToken prod_token(queue); moodycamel::ConsumerToken cons_token(queue); std::thread prod(producer_with_token, std::ref(prod_token)); std::thread cons(consumer_with_token, std::ref(cons_token)); prod.join(); cons.join(); return 0; }3. 批量操作这是性能最高的使用方式。cppvoid bulk_producer(moodycamel::ProducerToken token) { int items[100]; for (int i 0; i 100; i) { items[i] i; } // 一次性入队100个元素 queue.enqueue_bulk(token, items, 100); } void bulk_consumer(moodycamel::ConsumerToken token) { int items[50]; // 一次最多取出50个 size_t count; // 尝试批量出队count 是实际取出的数量 while ((count queue.try_dequeue_bulk(token, items, 50)) 0) { std::this_thread::yield(); } for (size_t i 0; i count; i) { std::cout items[i] std::endl; } }//批量读取最后所有数据以及释放 std::vectorint temp; temp.reserve(m_dataQueue.size_approx()); // 预估大小避免 vector 反复扩容 m_dataQueue.try_dequeue_bulk(std::back_inserter(temp), m_dataQueue.size_approx()); temp.clear();与其他队列的对比特性moodycamel::ConcurrentQueuestd::queue 互斥锁简单的无锁队列如基于 Michael-Scott并发模型无锁多生产者多消费者有锁无锁性能极高尤其在竞争激烈时差锁竞争是瓶颈中等CAS 操作可能成为瓶颈设计复杂度高内部使用块和令牌低中等内存使用较高预分配块低低适用场景高性能服务器、实时系统低并发、简单场景中等并发场景优缺点总结优点极致性能为高并发场景设计吞吐量非常大。功能全面支持阻塞/非阻塞操作、批量操作、令牌优化等。健壮性强经过广泛测试非常稳定。可移植性好纯 C11 实现跨平台。缺点代码复杂源代码非常复杂理解和调试困难。内存占用由于预分配块可能会比简单队列占用更多内存。API 稍显复杂为了获得最佳性能需要使用令牌和批量 API增加了使用复杂度。