
本文是 C 系列教程的第 24 篇。上一篇講解了線程與互斥鎖本篇深入同步機制進階條件變量與生產者消費者模型、原子類型與內存序、無鎖編程入門與讀寫鎖覆蓋 9 個完整示例代碼。一、條件變量std::condition_variable1.1 為什么需要條件變量互斥鎖只能保證「同時只有一個人進房間」但無法解決「等待某個條件成立」的問題。條件變量允許線程阻塞等待某個條件直到另一線程通知它醒來避免忙等待浪費 CPU#includeiostream#includethread#includemutex#includecondition_variableusingnamespacestd;mutex mtx;condition_variable cv;boolreadyfalse;voidwaiter(){unique_lockmutexlock(mtx);cv.wait(lock,[]{returnready;});// 阻塞直到 ready truecout等待者被喚醒開始工作endl;}voidnotifier(){this_thread::sleep_for(chrono::milliseconds(500));{lock_guardmutexlock(mtx);readytrue;// 修改條件必須在鎖內}cv.notify_one();// 喚醒一個等待線程}intmain(){threadt1(waiter);threadt2(notifier);t1.join();t2.join();return0;}wait(lock, predicate)的謂詞重載等價于while (!pred()) wait(lock)能自動處理虛假喚醒spurious wakeup。1.2 生產消費者模型條件變量最經典的應用場景。生產者往隊列放數據并通知消費者阻塞等待并從隊列取數據#includeiostream#includethread#includemutex#includecondition_variable#includequeueusingnamespacestd;mutex mtx;condition_variable cv;queueinttasks;booldonefalse;voidproducer(){for(inti1;i5;i){{lock_guardmutexlock(mtx);tasks.push(i);cout生產: iendl;}cv.notify_one();// 通知消費者this_thread::sleep_for(chrono::milliseconds(100));}{lock_guardmutexlock(mtx);donetrue;}cv.notify_all();// 喚醒所有消費者處理結束}voidconsumer(intid){while(true){unique_lockmutexlock(mtx);cv.wait(lock,[]{return!tasks.empty()||done;});if(!tasks.empty()){inttasktasks.front();tasks.pop();cout 消費者 id 消費: taskendl;}elseif(done){break;// 生產結束且隊列為空}}}intmain(){threadp(producer);threadc1(consumer,1);threadc2(consumer,2);p.join();c1.join();c2.join();cout生產消費完成endl;return0;}關鍵點notify_one喚醒單個線程notify_all喚醒全部等待條件必須用while/謂詞重載以防虛假喚醒done標志防止消費者永久阻塞。二、原子操作std::atomic2.1 原子類型基礎std::atomicT提供無鎖或鎖內部實現的原子操作fetch_add、exchange、compare_exchange等保證讀-改-寫完整性無需互斥鎖#includeiostream#includethread#includeatomic#includevectorusingnamespacestd;atomicintcounter{0};voidincrement(){for(inti0;i100000;i)counter.fetch_add(1);}intmain(){vectorthreadthreads;for(inti0;i4;i)threads.emplace_back(increment);for(autot:threads)t.join();coutcounter counter.load()endl;// 400000無需加鎖return0;}fetch_add原子完成「讀-加-寫」。load()原子讀取store()原子寫入。2.2 compare_exchange 與自旋鎖compare_exchange_strong是 CAS 指令的封裝當前值等于期望值時寫入新值否則更新期望值為實際值。可用它實現自旋鎖#includeiostream#includethread#includeatomic#includevectorusingnamespacestd;classSpinLock{atomicboolflag{false};public:voidlock(){// 期望 false嘗試寫入 true失敗則自旋重試while(flag.exchange(true)){// 空轉等待可加 this_thread::yield() 讓出 CPU}}voidunlock(){flag.store(false);}};SpinLock spin;intcounter0;voidwork(){for(inti0;i50000;i){lock_guardSpinLocklock(spin);counter;}}intmain(){vectorthreadthreads;for(inti0;i4;i)threads.emplace_back(work);for(autot:threads)t.join();coutcounter counterendl;// 200000return0;}自旋鎖適合臨界區極短的場景臨界區長時應使用會阻塞的std::mutex避免浪費 CPU。三、內存序Memory Order3.1 為什么要關心內存序編譯器與 CPU 可能重排指令單線程不可見多線程可導致意外行為。內存序控制重排邊界#includeiostream#includethread#includeatomicusingnamespacestd;atomicboolready{false};intdata0;voidproducer(){data42;// 寫數據ready.store(tru e,memory_order_release);// 釋放語義之前的寫操作全部可見}voidconsumer(){while(!ready.load(memory_order_acquire)){}// 獲取語義確保讀到最新 datacoutdata dataendl;// 保證讀到 42}intmain(){threadt1(producer);threadt2(consumer);t1.join();t2.join();return0;}release寫側與acquire讀側配對使用形成同步關系happens-before保證生產者寫入的數據對消費者可見。3.2 常見內存序對比內存序語義用途memory_order_relaxed無同步僅保證原子性計數器、統計量memory_order_acquire其后讀寫不可越過本操作讀取標志位memory_order_release其前讀寫不可越過本操作發布數據memory_order_acq_relacquire releaseRMW 操作memory_order_seq_cst全序一致默認復雜同步易推理#includeiostream#includethread#includeatomicusingnamespacestd;atomiclonglonghits{0};voidreport(){for(inti0;i1000000;i){hits.fetch_add(1,memory_order_relaxed);// 只需原子性無需同步}}intmain(){threadt1(report);threadt2(report);t1.join();t2.join();couthits hits.load(memory_order_relaxed)endl;// 2000000return0;}經驗默讙 seq_cst 最容易正確性能足夠時優先使用只有基準測試證明是瓶頸才降級為 relaxed/acquire/release 并仔細論證正確性。四、實戰線程安全的任務隊列綜合運用互斥鎖、條件變量與 RAII 封裝一個可直接復用的線程安全隊列#includeiostream#includethread#includemutex#includecondition_variable#includequeue#includeoptionalusingnamespacestd;templatetypenameTclassThreadSafeQueue{mutablemutex mtx;condition_variable cv;queueTq;public:voidpush(T value){{lock_guardmutexlock(mtx);q.push(move(value));}cv.notify_one();}// 阻塞彈出Tpop(){unique_lockmutexlock(mtx);cv.wait(lock,[this]{return!q.empty();});T valuemove(q.front());q.pop();returnvalue;}// 非阻塞嘗試彈出optionalTtryPop(){lock_guardmutexlock(mtx);if(q.empty())returnnullopt;T valuemove(q.front());q.pop();returnvalue;}size_tsize()const{lock_guardmutexlock(mtx);returnq.size();}};intmain(){ThreadSafeQueueinttq;threadproducer([]{for(inti1;i6;i){tq.push(i);this_thread::sleep_for(chrono::milliseconds(50));}});threadconsumer([]{for(inti0;i6;i){intvtq.pop();// 阻塞等待cout取出: v隊列剩余 tq.size()endl;}});producer.join();consumer.join();cout線程安全隊列測試完成endl;return0;}該隊列把鎖與條件變量的復雜性封裝在內部對外提供push/pop/tryPop/size四個安全接口是生產環境常用的基礎組件。總結本篇講解了并發同步進階技術condition_variable實現阻塞等待與通知含謂詞重載防虛假喚醒、生產者消費者模型的完整實現、atomic 原子類型fetch_add/CAS 自旋鎖避免數據競爭、內存序relaxed/acquire/release/seq_cst控制可見性與重排最后封裝了一個線程安全的任務隊列組件。建議配合互斥鎖按場景選用臨界區短用原子等待條件用條件變量常規保護用 mutex。下一篇將講解C 并發編程異步任務與線程池實戰std::async、future、packaged_task 與線程池實現敬請期待