
本文是 C 系列教程的第 23 篇。上一篇讲解了 C20 新特性本篇进入并发编程世界std::thread 线程创建与管理、互斥锁与数据竞争防护、死锁避免与锁的最佳实践覆盖 9 个完整示例代码。一、线程基础std::thread1.1 创建线程的三种方式std::thread在thread头文件提供。线程对象一旦构造完成立即开始执行可传入函数指针、函数对象或 Lambda#includeiostream#includethreadusingnamespacestd;voidworker(intid){cout线程 id 开始工作endl;this_thread::sleep_for(chrono::milliseconds(200));cout线程 id 工作结束endl;}intmain(){// 方式一函数指针threadt1(worker,1);// 方式二函数对象structTask{voidoperator()(intn)const{cout函数对象任务 nendl;}};threadt2(Task{},2);// 方式三Lambdathreadt3([](intn){coutLambda 任务 nendl;},3);t1.join();t2.join();t3.join();cout主线程结束endl;return0;}join()阻塞主线程直到子线程完成。必须对每个可 join 的线程调用join()或detach()否则析构时程序会std::terminate。1.2 join、detach 与线程参数传递#includeiostream#includethread#includestringusingnamespacestd;voidprintMsg(string msg,inttimes){for(inti0;itimes;i)coutmsg iendl;}intmain(){// 参数按值传递引用参数需用 std::refthreadt1(printMsg,hello,3);intcounter0;threadt2([counter]{for(inti0;i5;i)counter;});t1.join();// detach后台运行不阻塞主线程threadt3([]{cout后台线程运行endl;});t3.detach();t2.join();coutcounter counterendl;return0;}传引用给线程函数必须显式用std::ref包裹否则会按值拷贝。detach()后线程与主线程分离访问已销毁的局部变量会引发未定义行为需谨慎。二、数据竞争与互斥锁2.1 数据竞争演示多个线程同时读写共享变量会产生数据竞争data race结果是未定义的#includeiostream#includethread#includevectorusingnamespacestd;intmain(){intcounter0;vectorthreadthreads;for(inti0;i4;i){threads.emplace_back([counter]{for(intj0;j100000;j)counter;});}for(autot:threads)t.join();// 期望 400000实际可能远小于该值数据竞争coutcounter counterendl;return0;}counter不是原子操作读-改-写三步多线程交错执行导致计数丢失。运行多次会得到不同结果。2.2 用 std::mutex 保护临界区std::mutex提供互斥访问lock()/unlock()成对使用。更推荐用 RAII 封装的std::lock_guard或std::unique_lock异常安全且不会忘记解锁#includeiostream#includethread#includemutex#includevectorusingnamespacestd;mutex mtx;intcounter0;voidsafeIncrement(){lock_guardmutexlock(mtx);// 构造时加锁析构时自动解锁counter;}intmain(){vectorthreadthreads;for(inti0;i4;i){threads.emplace_back([]{for(intj0;j100000;j)safeIncrement();});}for(autot:threads)t.join();coutcounter counterendl;// 稳定输出 400000return0;}2.3 lock_guard 与 unique_lock 对比特性lock_guardunique_lock加锁方式构造即锁不可手动解锁可延迟加锁、手动解锁移动语义不支持支持配合条件变量不适用需 unlock适用开销更小略大状态标志#includeiostream#includemutex#includethreadusingnamespacestd;mutex mtx;intshared0;voiddoWork(boolcritical){unique_lockmutexlock(mtx,defer_lock);// 延迟匠锁// ... 偹一些非临界区准备工作 ...lock.lock();// 需要时前办宜标锁shared;lock.unlock();// 提前解锁// ... 临界区之外y: %yy.#yce yllydHB[XZ[HˆXYJܚYJNˆXYܚ[JNˆK[ Nˆ[ Nˆ]\YH\Y[ˆ]\ŸB9.x yfe y.#oacy 略 ##3.1死锁的产生 两个线程各持有一把锁又互相等待对方释放形成循环等待即死锁 cpp#includeiostream#includemutex#includethreadusingnamespacestd;mutex mtxA,mtxB;voidthread1(){lock_guardmutexa(mtxA);this_thread::sleep_for(chrono::milliseconds(10));lock_guardmutexb(mtxB);// 等待 mtxB被线程2持有cout线程1完成endl;}voidthread2(){lock_guardmutexb(mtxB);this_thread::sleep_for(chrono::milliseconds(10));lock_guardmutexa(mtxA);// 等待 mtxA被线程1持有→ 死锁cout线程2完成endl;}intmain(){threadt1(thread1);threadt2(thread2);t1.join();t2.join();return0;}程序将永远卡住。死锁四要素互斥、持有并等待、不可剥夺、循环等待破坏任一即可避免。3.2 用 std::lock 一次性锁定多把锁std::lock可原子性地同时锁定多个互斥体避免死锁再配合std::scoped_lockC17更简洁#includeiostream#includemutex#includethreadusingnamespacestd;mutex mtxA,mtxB;voidsafeWork(intid){// scoped_lock 同时锁定两把锁内部用 std::lock 算法无死锁scoped_locklock(mtxA,mtxB);cout线程 id 同时持有两把锁执行endl;this_thread::sleep_for(chrono::milliseconds(20));}intmain(){threadt1(safeWork,1);threadt2(safeWork,2);t1.join();t2.join();cout无死锁正常结束endl;return0;}实践原则全局统一加锁顺序如总是先 A 后 B或使用std::lock/scoped_lock同时锁定多个锁避免循环等待。四、实战多线程任务累加器综合运用线程、互斥锁、锁保护与计时的小工具把一个大任务拆成多个线程并行计算#includeiostream#includethread#includemutex#includevector#includechronousingnamespacestd;mutex mtx;longlongtotal0;// 每个线程计算一段 [start, end] 的累加voidpartialSum(longlongstart,longlongend){longlonglocal0;for(longlongistart;iend;i)locali;lock_guardmutexlock(mtx);// 只保护最后合并结果totallocal;}intmain(){constlonglongN10000000;// 1 加到 1千万constintnumThreads4;vectorthreadthreads;longlongchunkN/numThreads;autobeginchrono::high_resolution_clock::now();for(intt0;tnumThreads;t){longlongstartt*chunk1;longlongend(tnumThreads-1)?N:(t1)*chunk;threads.emplace_back(partialSum,start,end);}for(autoth:threads)th.join();autoendchrono::high_resolution_clock::now();doublemschrono::durationdouble,milli(end-begin).count();// 理论值 N*(N1)/2cout结果: total期望 50000005000000endl;cout耗时: ms ms线程数: numThreadsendl;return0;}关键技巧每个线程先算局部结果只在合并时动销把锁竞争降到最低比每个元素都动销快几个数量级。总结本篇讲解了 C 并发编程基础std::thread的创建/join/detach 与参数传递、数据竞争的危害、std::mutex与 RAII 封装lock_guard / unique_lock / scoped_lock保护共享数据、死锁的产生与避免统一加锁顺序 / std::lock 同时锁定最后用一个多线程累加器演示「局部计算 短临界区合并」的性能最佳实践。下一篇将讲解C 并发编程条件变量与原子操作生产消费者模型、condition_variable、atomic 与内存序敬请期待