C++多线程中锁与条件变量的异常阻塞问题
分析与修复你的C++多线程同步问题
我来帮你拆解下这个多线程同步的问题,你的代码里有几个关键逻辑错误导致了程序卡住和CPU高占用,我们一步步梳理并修复:
核心问题点
1. 解锁后调用cv.wait触发未定义行为
在main函数中,你先执行了lk.unlock(),之后直接用这个已经解锁的unique_lock调用cv.wait(lk, ...)。condition_variable::wait要求传入的锁必须处于锁定状态,解锁状态下调用会触发未定义行为——这就是为什么偶尔加个cout就“正常”了,那只是竞争条件下的偶然巧合,不是真的解决了问题。
2. 循环notify_one导致CPU空转
比如T1里的while (dataz) cv.notify_one();和main里的while (datax) cv.notify_one();,这种循环完全没必要:条件变量的通知只需要触发一次,被唤醒的线程会自行检查条件是否满足,循环通知只会让CPU无意义空转(这就是你看到CPU占用率高的原因)。
3. 状态变量的逻辑闭环混乱
原代码里的datax、dataz、zPrinted三个状态变量的切换逻辑不够清晰,且部分场景下可能出现数据竞争,导致线程读取到脏状态。
修复后的代码
#include "stdafx.h" #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <functional> using namespace std; mutex m; condition_variable cv; bool x_ready = false; // 标记main已输入x,T1可计算 bool z_calc_done = false;// 标记T1已算出z,T2可打印 bool z_print_done = false;// 标记T2已打印z,main可继续输入 void T1(const int& x, int& z) { while (true) { unique_lock<mutex> lk(m); // 等待main输入完成并标记x_ready cv.wait(lk, []{ return x_ready; }); z = x * x; x_ready = false; z_calc_done = true; lk.unlock(); cv.notify_one(); // 通知T2可以打印z } } void T2(const int& z) { while (true) { unique_lock<mutex> lk(m); // 等待T1完成计算并标记z_calc_done cv.wait(lk, []{ return z_calc_done; }); cout << "z = " << z << endl; z_calc_done = false; z_print_done = true; lk.unlock(); cv.notify_one(); // 通知main可以继续输入 } } int main() { int x, z; char c; thread threadT1(T1, cref(x), ref(z)); thread threadT2(T2, cref(z)); do { unique_lock<mutex> lk(m); cout << "Enter x: "; cin >> x; x_ready = true; lk.unlock(); cv.notify_one(); // 通知T1开始计算 // 等待T2打印完成,确保wait时锁处于锁定状态 unique_lock<mutex> wait_lk(m); cv.wait(wait_lk, []{ return z_print_done; }); z_print_done = false; cout << "Continue? (y/n): "; cin >> c; } while (c == 'y'); // 实际场景中需添加线程终止逻辑,这里暂时用detach简化 threadT1.detach(); threadT2.detach(); return 0; }
关键改动说明
- 状态变量重命名:用
x_ready、z_calc_done、z_print_done明确每个状态的含义,避免逻辑混淆。 - 移除无意义的循环通知:每个状态变更后只调用一次
cv.notify_one(),足够唤醒等待的线程。 - 确保wait时锁的状态正确:main函数中等待打印完成时,重新创建了一个锁定的
unique_lock,彻底避免未定义行为。 - 梳理逻辑闭环:
- main输入x → 标记
x_ready→ 通知T1 - T1计算z → 标记
z_calc_done→ 通知T2 - T2打印z → 标记
z_print_done→ 通知main - main等待
z_print_done后继续循环
- main输入x → 标记
这样整个同步逻辑清晰可控,不会出现死锁或CPU空转的问题。
内容的提问来源于stack exchange,提问作者C. Cappello
相关产品推荐
相关产品推荐

