ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

moodycamel::ConcurrentQueue 实战指南:C++11 工业级多生产者多消费者无锁队列的用法、预分配与设计原理

moodycamel::ConcurrentQueue 实战指南:C++11 工业级多生产者多消费者无锁队列的用法、预分配与设计原理 并发编程【免费下载链接】concurrentqueueA fast multi-producer, multi-consumer lock-free concurrent queue for C11项目地址https://gitcode.com/GitHub_Trending/co/concurrentqueue点击查看免费下载moodycamel::ConcurrentQueue 是当前仓库co/concurrentqueue的核心交付物一个为 C11 打造的工业级多生产者多消费者MPMC无锁并发队列。本文以仓库根目录的 README.md 为骨架结合 concurrentqueue.h、blockingconcurrentqueue.h、samples.md 以及测试与基准代码完整讲解它的特性边界、基本用法、Token 机制、批量操作、内存预分配公式、异常安全契约、Traits 定制以及底层块存储 每生产者子队列的高层设计。读完本文你将能够在自己的 C11 项目中正确、高效地集成并使用该队列并理解它为什么快、以及在哪里需要谨慎。特性总览README 中对这个队列的定位是能让你惊艳的快速blazing fast performance其核心特性清单如下单头文件实现整个队列的实现只包含在一个头文件里直接丢进项目即可使用。完全线程安全、无锁任意数量的线程可以同时读写。C11 实现元素在可能的情况下使用移动语义move而不是拷贝。模板化元素内存由队列代为管理不必只跟指针打交道。元素类型与数量不受人为限制。内存既可以一次性预先分配也可以按需动态分配。完全可移植不依赖任何内联汇编全部通过标准 C11 原语实现。支持超高速的批量bulk操作。附带低开销的阻塞版本BlockingConcurrentQueue。异常安全exception safe。这些声明均可在源码中得到印证moodycamel::ConcurrentQueue是模板类声明于 concurrentqueue.h构造函数同时支持只给初始容量与给出最小容量 最大显式/隐式生产者数两种形态concurrentqueue.h阻塞版本则位于独立的 blockingconcurrentqueue.h内部组合了普通队列与一个轻量信号量来自 lightweightsemaphore.h。为什么选择它以及为什么有时不该选它相比同类方案的差异README 给出了作者选择自研的动机可作为选型参考Boost的 lock-free queue 只支持平凡赋值运算符与平凡析构函数的对象Intel TBB的队列并非无锁且同样要求平凡构造函数学术界有大量关于 C 无锁队列的论文但可用的源码和测试非常难找。而moodycamel::ConcurrentQueue在限制更少的同时还提供批量入队/出队等高级能力。README 特别指出得益于新设计批量操作比逐元素操作快得多即使在激烈竞争heavy contention下也接近甚至超越非并发队列的速度。三个重要的使用前提Reasons not to useREADME 明确列出了该队列的固有边界使用时务必清楚不是线性化not linearizable的设计基础假设生产者相互独立。如果多个生产者线程之间做了额外的同步协调那么元素不一定会按照协调关系形成的全局顺序出队但每个单独生产者放入的顺序是保留的。如果你需要这种强顺序保证可以考虑其他实现或者使用同一个生产者 Token 从多个线程入队记得对该 Token 的访问做同步这样可以借助单一生产者子队列重建总序。不是 NUMA 感知的队列内部大量复用内存在 NUMA 架构上扩展性可能不佳。不是顺序一致sequentially consistent的元素入队与出队之间存在 happens-before 关系但把队列泵空直到为空这类操作在弱内存序下需要额外的内存屏障才能在所有场景下正确。README 建议尽量参照 samples.md 中的写法。作为权衡正是这种对弱内存序的利用换来了更高的性能。高层设计块存储 每生产者子队列README 用很短篇幅概括了核心架构值得展开元素内部用连续的块contiguous blocks存储而不是链表这是性能的关键队列由若干**子队列sub-queue**组成每个生产者对应一个子队列消费者出队时依次检查各个子队列直到找到非空的一个这一切对用户基本透明mostly just works。从源码结构看这一设计对应 concurrentqueue.h 中两类内部生产者显式生产者explicit producer与 Token 生命周期绑定、持有内存但更快与隐式生产者implicit producer向父队列回收更多内存但稍慢二者各有 enqueue / dequeue / bulk 版本四个核心方法并共享同一个块结构README 源码导览 中描述的内部结构顺序依次是无锁空闲链表 → 块结构 → 两类 SPMC 生产者队列 → 生产者链表 → 隐式生产者查找表。设计上的一个重要推论两个生产者同时入队时元素后来的出队顺序没有定义。如前所述除非你期望跨生产者的总序否则通常不受影响。快速上手Basic use包含头文件与编译器要求整个队列在一个头文件中concurrentqueue.h。阻塞版本在 blockingconcurrentqueue.h它依赖 concurrentqueue.h 与 lightweightsemaphore.h。实现依赖若干关键 C11 特性因此需要较新的编译器例如 VS2012 或 g 4.8注意 g 4.6 在std::atomic上有已知 bug不受支持。算法实现本身与平台无关。最小示例#include concurrentqueue.h moodycamel::ConcurrentQueueint q; q.enqueue(25); int item; bool found q.try_dequeue(item); assert(found item 25);用法与普通模板队列几乎一致区别只是可以同时被多个线程使用。基本方法说明方法语义ConcurrentQueue(size_t initialSizeEstimate)构造函数可选地传入对队列将容纳元素数量的估算enqueue(T item)入队一个元素必要时分配额外空间try_enqueue(T item)入队一个元素但仅在已有足够内存时成功try_dequeue(T item)出队一个元素找到返回true队列看起来为空返回false构造/析构的同步责任构造用户必须保证队列对象在交给其他线程使用之前完全构造完成包括通过内存屏障等让构造的内存效果对其他线程可见。析构所有线程必须已经停止使用该队列且内存效果已完全传播之后才能析构它。显式带 Token与隐式不带 Token方法几乎每个操作都有两个版本接收用户分配的 per-producer / per-consumerToken的显式版本以及不需要 Token 的隐式版本。显式版本几乎总是更快虽然不一定快很多。二者在入队时还有一个关键行为差异使用隐式入队方法会自动分配线程本地的生产者子队列thread-local producer sub-queue显式生产者则直接与 Token 的生命周期绑定内部会被回收复用。为避免子队列数量无限增长隐式生产者在线程退出后会被标记为可复用——但这一机制并非所有平台都支持。因此如果使用短生命周期线程README 建议改用显式生产者 Token。完整 API伪代码# 必要时分配更多内存 enqueue(item) : bool enqueue(prod_token, item) : bool enqueue_bulk(item_first, count) : bool enqueue_bulk(prod_token, item_first, count) : bool # 内存不足时失败 try_enqueue(item) : bool try_enqueue(prod_token, item) : bool try_enqueue_bulk(item_first, count) : bool try_enqueue_bulk(prod_token, item_first, count) : bool # 尝试出队从不分配内存 try_dequeue(item) : bool try_dequeue(cons_token, item) : bool try_dequeue_bulk(item_first, max) : size_t try_dequeue_bulk(cons_token, item_first, max) : size_t # 如果你恰好知道想从哪个生产者出队 try_dequeue_from_producer(prod_token, item) : bool try_dequeue_bulk_from_producer(prod_token, item_first, max) : size_t # 元素总数的一个不保证精确的计数 size_approx() : size_t以上方法与源码一一对应例如enqueue的四个重载位于 concurrentqueue.htry_enqueue位于 concurrentqueue.htry_dequeue/try_dequeue_bulk分别位于 concurrentqueue.h 与 concurrentqueue.htry_dequeue_from_producer与try_dequeue_bulk_from_producer位于 concurrentqueue.hsize_approx位于 concurrentqueue.h。阻塞版本BlockingConcurrentQueue在常规接口之上阻塞版本额外提供wait_dequeue与wait_dequeue_bulk方法并支持带超时的版本——超时单位既可以是微秒也可以是std::chrono对象。该封装开销极低但由于需要用轻量信号量记账比非阻塞版本略慢。唯一的主要注意事项千万不要在有人正在wait的时候销毁队列。这通常意味着在调用阻塞方法之前你要能确定后面一定还会再来一个元素。公平地说非阻塞版本同样不能在有人使用时销毁但阻塞场景下的清理协调更难。阻塞示例完整#include blockingconcurrentqueue.h moodycamel::BlockingConcurrentQueueint q; std::thread producer([]() { for (int i 0; i ! 100; i) { std::this_thread::sleep_for(std::chrono::milliseconds(i % 10)); q.enqueue(i); } }); std::thread consumer([]() { for (int i 0; i ! 100; i) { int item; q.wait_dequeue(item); assert(item i); if (q.wait_dequeue_timed(item, std::chrono::milliseconds(5))) { i; assert(item i); } } }); producer.join(); consumer.join(); assert(q.size_approx() 0);从源码看BlockingConcurrentQueue内部持有一个ConcurrentQueue与一个LightweightSemaphoreblockingconcurrentqueue.h并且信号量的自旋次数由Traits::MAX_SEMA_SPINS控制默认 10000见下文 Traits。这也解释了 README 中阻塞版本略慢的原因每次阻塞/唤醒都要经过信号量的记账。高级特性TokensToken 机制Token 为每个线程/任务提供额外的 per-producer、per-consumer 存储从而加速操作。Token 本身不是线程安全的需要同一时刻只被一个生产者/消费者使用它也不与某个具体线程绑定。使用方式是把 Token 作为方法的第一个参数moodycamel::ConcurrentQueueint q; moodycamel::ProducerToken ptok(q); q.enqueue(ptok, 17); moodycamel::ConsumerToken ctok(q); int item; q.try_dequeue(ctok, item); assert(item 17);如果你恰好知道要从哪个生产者消费例如单生产者、多消费者场景可以使用接收生产者 Token 的try_dequeue_from_producer系列方法省去一部分开销。Token 同样适用于阻塞版本。从源码看ProducerTokenconcurrentqueue.h析构时会把自己的生产者标记为inactive并释放关联valid()用于判断 Token 是否仍有效concurrentqueue.hToken 支持移动构造与移动赋值内部 swap但禁止拷贝。效率排序建议与 Token 数量纪律当需要大量生产/消费时最高效到最低效的用法依次是带 Token 的批量方法不带 Token 的批量方法带 Token 的单元素方法不带 Token 的单元素方法但注意不要随意创建 Token——理想情况是每种 Token 每线程一个。队列在给什么用什么的前提下都能工作但配 Token 时性能最佳。批量操作Bulk operations得益于队列的新颖设计批量入队/出队和单元素一样简单但开销被大幅削减moodycamel::ConcurrentQueueint q; int items[] { 1, 2, 3, 4, 5 }; q.enqueue_bulk(items, 5); int results[5]; // 也可以是任何迭代器 size_t count q.try_dequeue_bulk(results, 5); for (size_t i 0; i ! count; i) { assert(results[i] items[i]); }注意enqueue_bulk的第二个参数是元素数量try_dequeue_bulk的返回值是实际出队的元素数量可能小于请求的max。预分配如何正确使用 try_enqueuetry_enqueue与普通enqueue的关键区别是try_enqueue永远不会分配内存空间不足时直接返回false。因此正确使用的关键是为期望的最大元素数预先分配足够的空间。问题是队列以块block为单位工作而不是单个元素所以传入构造函数的数值换算并不直观。需要记住三点传入的数量会被向上取整到块大小的整数倍默认块大小是 32可通过 Traits 修改一个槽位被入队后要等整个块被填满再被完全清空后才能复用因此存在部分填充块的开销显式生产者和隐式生产者回收/认领块的方式不同需要的块数也不同。假设你希望队列在任意时刻至少能容纳N个元素以下是预分配元素数的公式除法为算术除法而非整数除法以便ceil()正常工作显式生产者使用 Token 入队(ceil(N / BLOCK_SIZE) 1) * MAX_NUM_PRODUCERS * BLOCK_SIZE隐式生产者不带 Token(ceil(N / BLOCK_SIZE) - 1 2 * MAX_NUM_PRODUCERS) * BLOCK_SIZE混合生产者类型((ceil(N / BLOCK_SIZE) - 1) * (MAX_EXPLICIT_PRODUCERS 1) 2 * (MAX_IMPLICIT_PRODUCERS MAX_EXPLICIT_PRODUCERS)) * BLOCK_SIZE如果嫌公式麻烦可以使用接受最小元素数N 最大显式/隐式生产者数的构造函数重载让它替你计算。源码中该构造函数确实内置了与混合公式一致的算法concurrentqueue.hsize_t blocks (((minCapacity BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers 1) 2 * (maxExplicitProducers maxImplicitProducers);除了块还有哪些容量限制除了块之外队列内部还有其他需要扩容即分配内存的数据结构。如果只用try_enqueue初始大小一旦被超过后续try_enqueue就会失败。具体受以下 Traits 限制INITIAL_IMPLICIT_PRODUCER_HASH_SIZE限制同时活跃的隐式生产者数量超过后内部哈希表需要扩容IMPLICIT_INITIAL_INDEX_SIZE限制单个隐式生产者未消费元素未填满块数量超过后其内部索引需要扩容EXPLICIT_INITIAL_INDEX_SIZE限制单个显式生产者未消费元素数量超过后其内部索引需要扩容。所以使用try_enqueue时除了按上面的公式配好块数还必须相应调大这几个 Traits 的初始值。最坏情况依然要处理失败最后要清醒认识一点由于队列是**最终一致eventually consistent**的且为了速度利用了弱内存序即使在正确预分配的前提下竞争激烈时try_enqueue仍可能失败例如某线程可能误以为队列已满。因此无论如何都要处理失败分支比如循环重试直到成功除非你不在乎丢元素。异常安全Exception safety队列本身是异常安全的使用可能抛异常的元素类型时不会损坏。队列自身从不抛异常——内存分配失败时操作会优雅地失败返回false而不是抛出std::bad_alloc。异常安全保证的前提条件元素类型的析构函数永不抛出传给批量操作的迭代器永不抛出。特别注意std::back_inserter它插入的目标容器可能因扩容而抛出std::bad_alloc因此请先为目标容器 reserve 足够的容量。具体保证如下入队如果元素的构造函数抛出异常入队操作会完全回滚。对批量入队而言这意味着在可能抛异常的情况下元素会被拷贝而非移动避免只移动了部分对象非批量入队总是优先使用移动构造函数如果有的话。出队如果赋值运算符在出队期间抛出单元素与批量均如此元素视为已出队异常传播前所有已出队的元素都会被正确析构但这些元素本身已经拿不回来了。任何异常都会沿调用栈向上传播此时队列处于一致状态。性能提示如果你的类型的拷贝/移动构造、赋值运算符不会抛异常请务必用noexcept标注——这能让队列在可能的情况下省掉异常检查开销即便零成本异常机制也仍存在代码体积影响。Traits定制队列行为队列支持一个 Traits 模板参数用于定义各种类型、常量以及队列使用的内存分配/释放函数。典型做法是继承默认 Traits 并只覆盖想改的项struct MyTraits : public moodycamel::ConcurrentQueueDefaultTraits { static const size_t BLOCK_SIZE 256; // 使用更大的块 }; moodycamel::ConcurrentQueueint, MyTraits q;默认 Traits 定义在 concurrentqueue.h完整成员及其默认值与含义如下Traits 成员默认值含义size_tstd::size_t通用大小类型index_tstd::size_t入队/出队索引类型须不小于size_t。建议明显大于预期同时容纳的元素数高周转场景尤其如此32 位 x86 上若短期内吞吐数千万元素32 位类型可能触发竞态推荐 64 位类型其无锁性取决于平台std::atomicstd::uint64_t是否无锁BLOCK_SIZE32块大小必须是 2 的幂。元素少而生产者多时宜用小块生产者少和/或元素多时宜用大块EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD32显式生产者检查块是否为空的方式切换阈值超过该值从逐标志迭代切换为原子计数器必须是 2 的幂EXPLICIT_INITIAL_INDEX_SIZE32单个显式生产者可预期的满块数上限影响try_enqueue容量必须是 2 的幂IMPLICIT_INITIAL_INDEX_SIZE32单个隐式生产者可预期的满块数上限同理必须是 2 的幂INITIAL_IMPLICIT_PRODUCER_HASH_SIZE32线程 ID → 隐式生产者哈希表的初始大小哈希每次半满时扩容必须为 2 的幂且为 0 时禁用隐式生产即不带 Token 的enqueue直接返回falseEXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE256显式消费者在触发全体消费者轮换到下一个内部队列之前必须消费的元素数MAX_SUBQUEUE_SIZE无符号整数最大值单个子队列可入队的最大元素数含按块级强制执行向上取整到块大小MAX_SEMA_SPINS10000等待信号量时先自旋的次数消费者线程数超过空闲核心数时建议 0-100否则 1000-10000只影响 BlockingConcurrentQueueRECYCLE_ALLOCATED_BLOCKSfalse是否把动态分配的块回收进内部空闲链表为false时只回收预分配块其余归还堆。注意显式生产者消费的块无论该值如何都只在队列析构时释放malloc/freestd::malloc/std::free可定制的内存分配函数malloc失败应返回nullptr对齐行为与std::malloc一致这些常量在队列类中都有对应的static_assert约束如BLOCK_SIZE必须为大于 1 的 2 的幂、index_t不得窄于size_t等见 concurrentqueue.h编译期即可发现配置错误。如何出队无需调用构造函数的类型常规出队方式是把一个已存在的对象按引用传入由队列内部对其赋值优先使用移动赋值。这对构造昂贵或没有默认构造函数的类型很成问题。README 给出了两种技巧技巧一内存拷贝包装器穷人的移动——注意仅当对象不含内部指针时才可用struct MyObjectMover { inline void operator(MyObject obj) { std::memcpy(data, obj, sizeof(MyObject)); // TODO: 清理 obj使其被队列析构时不会破坏刚移入的数据 } inline MyObject obj() { return *reinterpret_castMyObject*(data); } private: align(alignof(MyObject)) char data[sizeof(MyObject)]; };技巧二更稳妥延迟构造——适合移动便宜但默认构造昂贵的类型先不构造等赋值时才用移动构造函数原地构造struct MyObjectMover { inline void operator(MyObject x) { new (data) MyObject(std::move(x)); created true; } inline MyObject obj() { assert(created); return *reinterpret_castMyObject*(data); } ~MyObjectMover() { if (created) obj().~MyObject(); } private: align(alignof(MyObject)) char data[sizeof(MyObject)]; bool created false; };实战样例速览Samples仓库根目录的 samples.md 提供了一批比 README 更完整的用法示例多数演示的是最简单的版本但都可以替换为 Token 与批量方法以获得更高速度。包括Hello queue单线程入队 123 个元素再全部出队Hello concurrency10 个生产者 10 个消费者线程的基础并发用法Bulk up上述场景的批量操作版本更快Producer/consumer model (simultaneous)8 生产 8 消费同时进行用原子计数器保证所有元素最终被消费注释强调在出队前先 fence若生产者已结束以及最后一个消费者须看到其他消费者的内存效果Producer/consumer model (simultaneous, blocking)阻塞版要求预先知道元素总量或另有协调机制否则消费者可能永久阻塞在消费者阻塞时销毁队列是未定义行为Producer/consumer model (separate stages)先全部生产再全部消费的分阶段模型此时没有使用阻塞版的意义Object pool不知道哪些线程会使用队列时用隐式方法实现跨线程对象池Threadpool task queue用BlockingConcurrentQueueTask的wait_dequeue实现线程池任务队列Multithreaded game loop用原子计数器追踪帧内待处理任务渲染前等待计数归零也可边等边帮忙出队Pump until empty单线程/多线程把队列泵空的写法强调需要内存屏障保证可见性Wait for a queue to become empty (without dequeueing)无法稳健地干等空队列建议自建原子计数器轮询若只求估算可用size_approx()注意在队列尚未稳定——即仍有线程在入队/出队、或旧操作的内存效果尚未传播——时size_approx()可能返回 0 而队列并未完全空。基准测试如何自行复现README 给出了自行运行基准的步骤Linux 上需要较新的 gWindows 上需要 MinGW 与部分 GnuWin32 工具cd build make benchmarks bin/benchmarks基准的构建入口是 benchmarks/makefile主程序在 benchmarks/benchmarks.cpp。仓库为对比准备了多种队列的包装头boost::lockfree::queuebenchmarks/boostqueue.h、TBBbenchmarks/tbbqueue.h、基于互斥锁的 benchmarks/lockbasedqueue.h、简单无锁实现 benchmarks/simplelockfree.h、标准库 benchmarks/stdqueue.h 以及 dlib 队列benchmarks/dlibqueue.h还附带了 benchmarks/cpuid.cpp 用于获取 CPU 信息。README 对结果的短评是它太快了尤其是批量方法以至于如果你真的用队列在做事队列不会成为瓶颈。测试与验证Tests该队列经过了相当多的验证手段这与大多数开源 lock-free 队列有源码没测试形成鲜明对比单元测试tests/unittests/unittests.cpp其中的runAllTests()作为可被测试宿主调用的入口unittests.cpp另有 tests/unittests/makefile 与轻量测试框架 tests/unittests/minitest.h随机化长期运行的模糊测试tests/fuzztests/fuzztests.cppCDSCheckerC11 内存模型检查器对核心队列算法进行了建模检查tests/CDSCheckerRelacy分别对部分内部算法与完整集成测试做了模型检查tests/relacy含 tests/relacy/integrated.cpp、tests/relacy/freelist.cpp、tests/relacy/spmchash.cpp 等。作者曾在 LinuxFedora 19与 Windows 7 的 x86Intel 与 AMD上测试CI 还会为riscv64Linux 在 QEMU 下交叉编译并运行单元测试代码按平台无关编写预期可在所有处理器与操作系统上工作。尽管如此README 坦言由于实现复杂度与 lock-free 代码本身难以测试的特性仍可能存在 bug遇到异常行为欢迎提交 issue最好附上可复现的单元测试。安装与集成通过 vcpkg 安装可以使用 vcpkg 依赖管理器安装./bootstrap-vcpkg.sh ./vcpkg integrate install vcpkg install concurrentqueue使用前需先按 vcpkg 官方说明克隆其仓库。vcpkg 中的该 port 由微软团队成员与社区贡献者维护更新。通过 CMake 集成仓库提供了 CMakeLists.txt定义了一个 INTERFACE 库concurrentqueue头文件目录指向仓库根目录并将 concurrentqueue.h、blockingconcurrentqueue.h、lightweightsemaphore.h 与 LICENSE.md 安装到include/concurrentqueue/moodycamel下同时借助 concurrentqueueConfig.cmake.in 生成可被find_package(concurrentqueue)消费的包配置文件并支持 RPM/DEB 打包CPack。此外仓库的 c_api 目录还提供了一套薄 C 语言绑定c_api/concurrentqueue.cpp 与 c_api/blockingconcurrentqueue.cpp方便在非 C 环境中调用。深入源码代码布局导览README 的最后一部分为想读源码的读者提供了地图结合实际文件可归纳为大致按 concurrentqueue.h 中的出现顺序辅助函数如向上取整到 2 的幂、align_for对齐等默认 Traitsconcurrentqueue.h队列用到的常量与 malloc/free 函数ProducerToken / ConsumerTokenconcurrentqueue.h 与 concurrentqueue.h公共 API构造函数L826、析构函数、swap/移动赋值L924-L1000、公开的 enqueue 方法全部是后方少量私有 enqueue 方法的包装、以及内联且相对直白的 dequeue 方法主要内部数据结构无锁空闲链表free list用于回收用尽的块块结构block有两种是否已完全清空的跟踪方式因为两个并行消费者无法预知谁先完成两类内部 SPMC 生产者队列的公共基类——显式生产者更占内存但更快与隐式生产者向父队列回收更多内存但稍慢各含 enqueue / dequeue / enqueue_bulk / dequeue_bulk 四个核心方法二者块处理方式不同索引到块的映射方式也不同杂项内部方法初始块池构造时填充与抽象块池初始池 空闲链表、生产者链表无锁的只增链表、隐式生产者查找表一种特化的 TLS 查找、对象分配/释放辅助方法、队列数据成员以及最后的自由函数swap。许可协议仓库源码排除第三方代码用于基准对比的 Boost 队列与 Intel TBB、CDSChecker、Relacy以及 Jeff Preshing 的跨平台信号量——它们各有自己的许可在简化 BSD 许可下发布并同时采用 Boost Software License 双许可详见 LICENSE.md。README 亦提醒lock-free 编程是专利雷区该代码可能作者未逐一核查涉及未决专利但作者声明该队列是从零自行设计实现的。输出文章赞分享并发编程【免费下载链接】concurrentqueueA fast multi-producer, multi-consumer lock-free concurrent queue for C11项目地址https://gitcode.com/GitHub_Trending/co/concurrentqueue点击查看免费下载相关推荐告别线程阻塞moodycamel::ConcurrentQueue单头文件实现极速多生产者多消费者并发队列告别线程阻塞moodycamel::ConcurrentQueue单头文件实现极速多生产者多消费者并发队列 为什么传统并发队列让开发者头疼 在多线程编程中并发编程终极指南POCO C多线程设计模式的生产者-消费者与读写锁实战终极指南POCO C多线程设计模式的生产者 消费者与读写锁实战 POCO C Libraries是一套功能强大的跨平台C库专为构建网络和互联网应后端网络/通信数据库密码学Web框架上一篇Animavita宠物领养平台如何快速找到您附近的宠物伙伴下一篇终极指南如何用UTF-8 C库轻松实现多语言Unicode处理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表