1. 项目概述为什么原子操作是多线程开发的基石在C多线程开发里我们经常听到“原子操作”这个词。很多开发者尤其是从单线程思维过渡过来的朋友一听到“原子”就觉得高深莫测下意识地还是想用互斥锁std::mutex来解决问题。锁确实简单直观一个lock()一个unlock()把临界区一包感觉世界就清净了。但当你真正面对一个高并发、低延迟的系统时比如一个高频交易引擎或者一个实时游戏服务器你会发现锁带来的性能开销和潜在的阻塞、死锁问题会成为系统瓶颈的罪魁祸首。原子操作就是那把帮你从“锁”的沉重负担中解放出来的钥匙。你可以把它理解为一种“微观层面的锁”但它锁住的不是一大段代码而是一个具体的、内存中的变量。它的核心承诺是对这个变量的读、写或修改操作从任何其他线程的视角看都是“一瞬间”完成的是不可分割的。这就好比你去银行柜台存钱原子操作保证了你“查看余额”和“更新余额”这两个动作在系统里被合并成一个不可中断的原子事务其他线程绝不会看到你“刚查完余额但还没存进去”的中间状态。我最初接触原子操作是为了优化一个日志统计模块。那个模块需要多个工作线程累加各种计数器比如错误次数、处理消息数。最开始用mutex保护一个int计数器性能测试时发现当线程数增加到8个以上纯粹加锁解锁的开销就占用了近15%的CPU时间。后来换用std::atomicint性能直接提升了不止一个数量级而且代码看起来更简洁。这个经历让我深刻体会到理解并善用原子操作是从一个合格的多线程程序员迈向资深的关键一步。它不仅仅是性能优化工具更是构建正确、高效并发数据结构的基石。接下来我们就抛开晦涩的理论从实际应用场景出发彻底搞懂C原子操作怎么用以及为什么要这么用。2. 核心概念解析原子性、内存顺序与无锁编程在深入代码之前我们必须先建立几个核心认知。这就像学武功要先扎马步基础不牢后面写的代码全是“内伤”。2.1 什么才是真正的“原子性”原子性的字面意思是“不可分割”。在CPU层面对一个简单类型如int的赋值比如x 42;真的就是原子的吗答案是否定的。在绝大多数现代架构上这个操作可能对应一条汇编指令但这条指令的执行仍然可能被中断比如发生线程切换或者从其他CPU核心的缓存视角看这个值的传播不是瞬间的。C标准中的std::atomic提供的原子性是语言级别和内存模型级别的保证。它确保了两件事操作本身的不可分割性例如自增fetch_add读取旧值、计算新值、写回新值这一系列操作作为一个整体其他线程看不到中间状态。操作结果的全局可见性一个线程对原子变量的修改最终会以某种确定的顺序被其他线程观察到。这里就引出了第二个更关键、也更让人头疼的概念——内存顺序。2.2 内存顺序理解多线程世界的“因果律”这是原子操作中最难啃的骨头但也是区分“会用”和“精通”的关键。你可以把内存顺序想象成多线程世界里的“消息传递规则”。默认情况下编译器为了优化可能会重排指令顺序CPU为了效率也会乱序执行指令。在单线程下这没问题因为最终结果一致。但在多线程下如果线程A和线程B观察到的变量修改顺序不一致就会导致逻辑错误。C提供了六种内存顺序从弱到强主要分为三类松散顺序 (memory_order_relaxed)只保证原子操作本身的原子性不提供任何同步或顺序保证。其他线程看到这个修改的顺序可能是任意的。它只适用于不需要同步只需要原子计数的场景比如性能计数器。// 示例一个简单的性能计数器顺序不重要只要计数准确 std::atomicint counter{0}; void increment() { counter.fetch_add(1, std::memory_order_relaxed); }释放-获取顺序 (memory_order_release/memory_order_acquire/memory_order_acq_rel)这是最常用、也最应该被掌握的顺序模型。它建立了线程间的“同步-发生前”关系。release释放通常用在写操作上。保证在这个release操作之前的所有内存读写包括非原子的都不会被重排到这个release操作之后。acquire获取通常用在读操作上。保证在这个acquire操作之后的所有内存读写都不会被重排到这个acquire操作之前。当一个release写操作与一个acquire读操作配对作用于同一个原子变量时就建立了一个同步点。写线程release之前的所有写操作对读线程acquire之后的操作都是可见的。这完美解决了“线程间传递非原子数据”的问题。// 示例安全地发布一个初始化完成的数据 std::atomicbool data_ready{false}; int shared_data 0; // 线程A初始化并发布数据 void thread_a() { shared_data 42; // 1. 初始化非原子数据 data_ready.store(true, std::memory_order_release); // 2. 发布标志保证1不会重排到2之后 } // 线程B等待并读取数据 void thread_b() { while(!data_ready.load(std::memory_order_acquire)) { // 3. 获取标志保证4不会重排到3之前 // 忙等待或休眠 } int value shared_data; // 4. 安全读取此时一定能看到42 }顺序一致顺序 (memory_order_seq_cst)这是默认的内存顺序也是最强的保证。它要求所有线程看到的所有原子操作的修改顺序都是一致的就像一个全局的时钟在给所有操作排序。它最容易理解但性能开销也最大。在x86这种强内存模型架构上seq_cst和acq/rel的开销可能差不多但在ARM等弱内存模型架构上seq_cst可能需要插入内存屏障指令代价较高。注意对于初学者我的建议是除非你非常确定自己在做什么否则在需要同步的场景优先使用release/acquire在只需要原子性如计数器的场景使用relaxed暂时不要轻易使用seq_cst除非你明确需要全局总序或者是在写一些无锁数据结构的核心算法。2.3 原子操作与无锁编程的关系很多人误以为用了原子操作就是无锁编程。其实原子操作是无锁编程的必要工具但远非全部。无锁Lock-Free是一种算法属性它保证在任意时刻至少有一个线程能够取得进展系统整体不会因为某个线程挂起而停止。更进一步的还有等待无关Wait-Free保证每个线程都能在有限步内完成操作。使用原子操作我们可以构建一些简单的无锁数据结构比如无锁栈、无锁队列。但请注意无锁编程极其复杂正确性验证困难一个细微的内存顺序错误就可能导致极难重现的Bug。对于大多数应用开发我的经验是用原子操作替代简单的锁用于保护单个标量或简单标志位用成熟的无锁库如boost::lockfree来实现复杂数据结构不要轻易自己从头实现无锁链表、树等复杂结构。3.std::atomic实战从基础API到高级用法理论说再多不如一行代码。C11在atomic头文件中提供了std::atomic模板类。它对整数类型int,long等、指针类型和bool提供了特化并支持所有主流平台。3.1 基础操作加载、存储与交换这些是最直接的操作对应着“读”、“写”和“读-改-写”中的基础部分。load(memory_order)原子地读取并返回原子变量的当前值。store(desired, memory_order)原子地将原子变量的值替换为desired。exchange(desired, memory_order)原子地将原子变量的值替换为desired并返回替换前的旧值。这是一个“读-改-写”操作。std::atomicint flag{0}; // 线程1设置标志 flag.store(1, std::memory_order_release); // 线程2读取并重置标志 int old_value flag.exchange(0, std::memory_order_acq_rel); // old_value 可能是1如果线程1已设置也可能是0如果其他线程已重置3.2 读-改-写操作并发编程的瑞士军刀这是原子操作的精髓它们在一个不可中断的操作中完成“读取当前值、根据当前值计算新值、写回新值”的全过程。fetch_add(arg, memory_order)/fetch_sub(arg, memory_order)原子地将变量加上或减去arg并返回加/减之前的值。这是实现计数器的核心。fetch_and(arg, memory_order)/fetch_or(arg, memory_order)/fetch_xor(arg, memory_order)原子地进行位与、位或、位异或操作返回操作前的值。compare_exchange_strong(expected, desired, memory_order)最强大也是最复杂的操作简称CASCompare-And-Swap。它检查原子变量的值是否与expected相等。如果相等则用desired替换它返回true如果不相等则将原子变量的实际值写入expected返回false。所有操作是原子的。compare_exchange_weak(expected, desired, memory_order)弱版本的CAS。它允许在某些平台上如某些ARM架构出现“伪失败”即即使值相等也返回false。因此它通常用在循环里。// 示例使用CAS实现一个简单的自旋锁 class SimpleSpinLock { std::atomicbool locked_{false}; public: void lock() { bool expected false; // 循环直到成功将locked_从false设置为true while (!locked_.compare_exchange_weak(expected, true, std::memory_order_acquire, std::memory_order_relaxed)) { expected false; // CAS失败后expected被更新为当前值(true)需要重置为期望的false // 可选加入退避策略如pause指令或yield避免过度消耗CPU // __builtin_ia32_pause(); // x86 // std::this_thread::yield(); } // 成功获取锁此时内存顺序为acquire保证锁保护区域内的读操作能看到之前释放锁线程的所有写 } void unlock() { locked_.store(false, std::memory_order_release); // 释放锁保证锁保护区域内的写操作对后续获取锁的线程可见 } };实操心得compare_exchange_weak在循环中通常比strong版本性能稍好因为weak允许失败编译器可能生成更优的代码。所以在循环中使用weak如果不在循环中比如只尝试一次则用strong。另外CAS循环是构建无锁数据结构如栈、队列的核心模式务必理解透彻。3.3 特化类型与atomic_flagstd::atomicT*原子指针。除了支持上述操作还特有fetch_add和fetch_sub用于原子地调整指针偏移这在无锁内存池中非常有用。std::atomicbool原子布尔值常用于标志位。std::atomic_flag这是一个与众不同的类型它保证是无锁的其他std::atomicT的实现可能在某些类型上不是无锁的可以用is_lock_free()成员函数检查。它只有两个操作test_and_set(memory_order)和clear(memory_order)。test_and_set将标志设置为true并返回之前的值clear将其设置为false。它是实现自旋锁的最低级原语。// 使用atomic_flag实现自旋锁C20前 class AtomicFlagSpinLock { std::atomic_flag flag_ ATOMIC_FLAG_INIT; // C20后可以直接默认初始化 public: void lock() { while(flag_.test_and_set(std::memory_order_acquire)) { // 忙等待 } } void unlock() { flag_.clear(std::memory_order_release); } };4. 典型应用场景与代码实战理解了工具我们来看看在什么场合下使用它们最合适。原子操作不是银弹它最适合解决特定类型的共享数据问题。4.1 场景一高性能计数器与状态标志这是原子操作最直接、最安全的用途。比如统计请求次数、在线用户数、任务完成数等。class Statistics { private: std::atomiclong long request_count_{0}; std::atomicint active_connections_{0}; std::atomicbool shutdown_requested_{false}; public: void on_request() { request_count_.fetch_add(1, std::memory_order_relaxed); // 顺序不重要原子性即可 } void connection_established() { active_connections_.fetch_add(1, std::memory_order_relaxed); } void connection_closed() { active_connections_.fetch_sub(1, std::memory_order_relaxed); } void request_shutdown() { shutdown_requested_.store(true, std::memory_order_release); // 发布关闭信号需要同步 } bool should_shutdown() const { return shutdown_requested_.load(std::memory_order_acquire); // 获取关闭信号 } // 获取当前快照注意在并发下这几个值不是“同一时刻”的但各自是准确的 void get_snapshot(long long count, int conns) const { count request_count_.load(std::memory_order_relaxed); conns active_connections_.load(std::memory_order_relaxed); } };4.2 场景二单次初始化与延迟初始化确保某个资源如全局配置、单例对象只被初始化一次且初始化过程线程安全。这比使用std::call_once或双检锁DCLP更底层、更灵活。class LazySingleton { private: static std::atomicLazySingleton* instance_; static std::mutex init_mutex_; LazySingleton() { /* 初始化成本高 */ } public: static LazySingleton* get_instance() { // 第一次检查避免每次调用都加锁内存序为acquire保证看到完整的初始化对象 LazySingleton* tmp instance_.load(std::memory_order_acquire); if (tmp nullptr) { std::lock_guardstd::mutex lock(init_mutex_); tmp instance_.load(std::memory_order_relaxed); // 锁内再次检查 if (tmp nullptr) { tmp new LazySingleton(); // 初始化完成后用release顺序发布指针。保证tmp对象的构造对所有后续acquire加载的线程可见 instance_.store(tmp, std::memory_order_release); } } return tmp; } }; std::atomicLazySingleton* LazySingleton::instance_{nullptr}; std::mutex LazySingleton::init_mutex_;注意事项这是“双检锁”模式的一个正确实现。关键在于load使用acquirestore使用release这构成了同步关系确保了new LazySingleton()中的所有写操作即对象的构造在store之前完成并对load之后的操作可见。早期的DCLP在Java或C中出错就是因为缺少这样的内存顺序约束。4.3 场景三无锁环形缓冲区生产者-消费者模型这是一个经典的高并发数据结构适用于一个线程生产数据另一个线程消费数据的场景。使用原子操作控制读写指针可以实现完全无锁。templatetypename T, size_t Capacity class LockFreeRingBuffer { static_assert((Capacity (Capacity - 1)) 0, Capacity must be a power of two); // 简化索引计算 T buffer_[Capacity]; std::atomicsize_t write_idx_{0}; std::atomicsize_t read_idx_{0}; size_t mask(size_t idx) const { return idx (Capacity - 1); } public: // 尝试推送一个元素成功返回true bool try_push(const T item) { size_t current_write write_idx_.load(std::memory_order_relaxed); size_t next_write current_write 1; size_t current_read read_idx_.load(std::memory_order_acquire); // 需要同步读指针 // 缓冲区满判断 if ((next_write - current_read) Capacity) { return false; } buffer_[mask(current_write)] item; // 写入数据 // 更新写指针使用release顺序保证上面的数据写入对消费者线程可见 write_idx_.store(next_write, std::memory_order_release); return true; } // 尝试弹出一个元素成功返回true bool try_pop(T item) { size_t current_read read_idx_.load(std::memory_order_relaxed); size_t current_write write_idx_.load(std::memory_order_acquire); // 需要同步写指针 // 缓冲区空判断 if (current_read current_write) { return false; } item buffer_[mask(current_read)]; // 读取数据 // 更新读指针使用release顺序保证此pop操作完成后空间可被生产者复用 read_idx_.store(current_read 1, std::memory_order_release); return true; } bool is_empty() const { // 注意这个判断在并发下是“模糊”的只能作为参考 return read_idx_.load(std::memory_order_acquire) write_idx_.load(std::memory_order_acquire); } };这个实现是一个单生产者单消费者SPSC的环形缓冲区。它利用了release/acquire配对来同步生产者和消费者生产者store(write_idx)与消费者load(write_idx)配对消费者store(read_idx)与生产者load(read_idx)配对从而安全地传递buffer_中的数据。4.4 场景四引用计数与智能指针std::shared_ptr的引用计数内部就是使用原子操作来保证线程安全的。我们可以借鉴其思想实现自己的线程安全对象生命周期管理。templatetypename T class ThreadSafeRefCounted { mutable std::atomicint ref_count_{1}; // mutable允许在const成员函数中修改 T data_; public: explicit ThreadSafeRefCounted(T data) : data_(std::move(data)) {} // 增加引用计数 void add_ref() const { ref_count_.fetch_add(1, std::memory_order_relaxed); } // 减少引用计数返回0表示需要销毁 int release() const { // 使用memory_order_acq_rel因为减1操作既需要看到之前所有的addacquire // 也需要将减1的结果发布出去release特别是当计数减到0时需要保证析构操作能看到对象的所有状态。 int old_count ref_count_.fetch_sub(1, std::memory_order_acq_rel); if (old_count 1) { // 此时只有当前线程持有最后一个引用可以安全销毁 // 使用memory_order_acquire屏障确保在删除对象前能看到所有线程对data_的修改 std::atomic_thread_fence(std::memory_order_acquire); delete this; } return old_count - 1; } // ... 其他访问data_的接口需要根据情况加锁或使用原子操作保护 };重要提示这是一个极度简化的示例用于说明原理。实际中std::shared_ptr的控制块实现要复杂得多因为它还要处理弱指针、自定义删除器、分配器等。永远不要在生产环境中自己重新实现std::shared_ptr直接使用标准库的是最安全、最正确的选择。5. 高级话题内存屏障、volatile误区与平台差异5.1std::atomic_thread_fence独立的内存屏障有时你不需要对一个具体的原子变量进行操作只是想在代码中插入一个全局的内存顺序约束。这时就需要内存屏障Fence。std::atomic_thread_fence(std::memory_order_release)在此屏障之前的所有写操作都不会被重排到此屏障之后。std::atomic_thread_fence(std::memory_order_acquire)在此屏障之后的所有读操作都不会被重排到此屏障之前。std::atomic_thread_fence(std::memory_order_seq_cst)最强的屏障同时具有release和acquire语义并参与全局顺序一致排序。屏障通常用于更复杂的无锁算法中当同步关系不便于通过单个原子变量的load/store来建立时。对于大多数应用级开发使用带内存序的原子变量操作就足够了。5.2volatile与std::atomic的根本区别这是一个经典的混淆点。volatile在C/C中不提供任何线程同步或原子性保证。它的语义是阻止编译器对该变量的读写进行优化例如认为它只读一次而缓存到寄存器。保证变量的读写操作严格按照源代码顺序执行相对于其他volatile变量。volatile是为内存映射I/O或信号处理函数中修改的全局变量设计的。在这些场景中变量的值可能被硬件或其他线程异步修改编译器不应该做任何假设。在并发编程中使用volatile代替atomic是绝对错误的。volatile不能保证操作的原子性比如volatile int的自增在多线程下仍是危险的也不能提供必要的内存顺序保证。记住这个口诀volatile管编译器atomic管多线程和内存可见性。5.3 平台差异与性能考量不同CPU架构有不同的内存模型x86/x86-64是强内存模型TSOTotal Store Order。它天然为大多数load和store操作提供了“获取-释放”语义。因此在x86上使用memory_order_acq_rel甚至seq_cst可能不需要生成额外的内存屏障指令性能代价较小。但relaxed操作仍然可以带来优化空间。ARM/PowerPC是弱内存模型。它们允许更多的指令重排。release和acquire语义需要明确的屏障指令如dmb来保证。因此在ARM上正确使用内存顺序对性能和正确性都至关重要。一个重要的检查是is_lock_free()。如果std::atomicT::is_lock_free()返回true说明该类型在该平台上的操作是真正的无锁实现通常使用CPU的原子指令如LOCK前缀或LL/SC指令。如果返回false编译器可能会在内部使用一个互斥锁来模拟原子性这就会丧失性能优势。对于内置的整数和指针类型在现代平台上通常都是无锁的。6. 调试、验证与常见陷阱原子操作和无锁代码的Bug往往难以复现依赖于特定的线程交错顺序。调试它们是一场噩梦。6.1 工具与调试技巧ThreadSanitizer (TSan)这是你的头号武器。它是Clang/GCC编译器提供的动态分析工具可以检测数据竞争、死锁、原子操作顺序违规等。在编译时添加-fsanitizethread标志运行时就能得到详细的竞争报告。静态分析一些高级的静态分析工具或模型检查器如Facebook的Infer或专门的并发模型检查器可以在代码层面发现潜在问题但能力有限。压力测试与模糊测试编写高并发、随机调度的测试用例让线程以尽可能多的交错顺序运行。运行数百万甚至上亿次增加发现隐藏Bug的概率。代码审查让有经验的同事审查你的无锁代码特别是内存顺序的使用。多一双眼睛能发现很多自己忽略的细节。简化与证明如果算法太复杂试着把它画出来用状态机或形式化方法如线性化点去推理其正确性。复杂的无锁算法往往意味着更高的风险。6.2 常见陷阱清单ABA问题这是CAS操作的一个经典陷阱。线程1读取原子变量值为A准备将其CAS为C。在此期间线程2将值从A改为B然后又改回A。线程1的CAS操作会成功但它基于的“状态A”的上下文已经无效了可能关联的其他数据已改变。解决方案使用带版本号的指针如std::atomicstd::pairT*, uintptr_t或依赖支持双字CASDCAS的平台指令。顺序反转错误地使用memory_order_relaxed导致逻辑错误。比如一个线程先设置数据再设置标志位另一个线程看到标志位后去读数据。如果都用relaxed读线程可能看到设置好的标志位但读到的却是未初始化的数据。丢失更新误以为var是原子的。对于非原子变量var是“读-改-写”三步多线程下会丢失更新。必须使用atomic的fetch_add。过度同步在不必要的地方使用memory_order_seq_cst导致性能损失。仔细分析线程间的同步需求选择最弱但足够的内存顺序。错误理解“无锁”写了一个基于原子操作的算法但里面包含了可能阻塞的操作如动态内存分配new/delete。这破坏了“无锁”的进展保证。无锁数据结构通常需要预分配内存或使用安全的内存回收方案如风险指针、epoch-based reclamation。7. 性能对比实测与选型建议纸上得来终觉浅我们用一个简单的基准测试来感受一下差异。测试场景多个线程并发递增一个计数器1000万次。#include atomic #include mutex #include vector #include thread #include chrono #include iostream void test_mutex(int num_threads, long long iterations) { long long counter 0; std::mutex mtx; auto start std::chrono::high_resolution_clock::now(); std::vectorstd::thread threads; for (int i 0; i num_threads; i) { threads.emplace_back([]() { for (long long j 0; j iterations; j) { std::lock_guardstd::mutex lock(mtx); counter; } }); } for (auto t : threads) t.join(); auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start).count(); std::cout Mutex [ num_threads threads]: duration ms, counter counter std::endl; } void test_atomic_relaxed(int num_threads, long long iterations) { std::atomiclong long counter{0}; auto start std::chrono::high_resolution_clock::now(); std::vectorstd::thread threads; for (int i 0; i num_threads; i) { threads.emplace_back([]() { for (long long j 0; j iterations; j) { counter.fetch_add(1, std::memory_order_relaxed); } }); } for (auto t : threads) t.join(); auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start).count(); std::cout Atomic Relaxed [ num_threads threads]: duration ms, counter counter.load() std::endl; } void test_atomic_seq_cst(int num_threads, long long iterations) { std::atomiclong long counter{0}; auto start std::chrono::high_resolution_clock::now(); std::vectorstd::thread threads; for (int i 0; i num_threads; i) { threads.emplace_back([]() { for (long long j 0; j iterations; j) { counter.fetch_add(1, std::memory_order_seq_cst); } }); } for (auto t : threads) t.join(); auto end std::chrono::high_resolution_clock::now(); auto duration std::chrono::duration_caststd::chrono::milliseconds(end - start).count(); std::cout Atomic Seq_Cst [ num_threads threads]: duration ms, counter counter.load() std::endl; } int main() { const long long iterations_per_thread 10000000LL; for (int num_threads : {1, 2, 4, 8}) { std::cout \n--- num_threads Thread(s) --- std::endl; test_mutex(num_threads, iterations_per_thread / num_threads); test_atomic_relaxed(num_threads, iterations_per_thread / num_threads); test_atomic_seq_cst(num_threads, iterations_per_thread / num_threads); } return 0; }在我的测试环境8核x86_64上结果趋势非常明显单线程三者差异不大互斥锁稍慢一点因为涉及系统调用。多线程2-8线程atomic_relaxed速度最快且随着线程数增加耗时增长平缓。atomic_seq_cst次之在x86上它与relaxed的差距可能不大但在弱内存模型CPU上差距会拉大。mutex最慢且随着竞争加剧耗时线性甚至更快速增长因为线程会频繁地挂起和唤醒上下文切换开销巨大。选型建议总结保护简单的标量数据标志、计数器无脑选择std::atomic并根据是否需要线程间同步决定使用relaxed还是release/acquire。保护一小段临界区代码涉及多个相关变量的修改使用std::mutex。代码简单不易出错。在竞争不激烈时现代操作系统的互斥锁优化得很好。需要复杂的同步逻辑或数据结构优先考虑更高级的同步原语如std::condition_variable,std::semaphore(C20),std::latch,std::barrier等。追求极限性能且临界区非常短小考虑使用std::atomic实现的自旋锁注意设置合理的自旋次数或退避策略或者使用无锁数据结构。但务必进行严格的正确性验证和性能剖析。除非你是并发库的开发者或者有极其确切的性能瓶颈证据否则不要轻易尝试自己实现复杂的无锁算法。使用成熟的库如英特尔TBB、boost::lockfree是更稳妥的选择。原子操作是C多线程工具箱中一把锋利的手术刀它精准、高效但使用不当也容易伤到自己。从理解release/acquire这对核心语义开始从小规模的计数器、标志位用起逐步积累经验和信心。当你能够游刃有余地使用原子操作来优化热点路径时你对程序执行的理解和对并发控制的把握就已经远超大多数开发者了。记住在并发编程中正确性永远凌驾于性能之上。