多线程生产者/消费者C++程序随机死锁与段错误问题排查求助
大家好,我最近在实现一个基于多线程的生产者/消费者模型,目标是避免死锁,但运行时状况很不稳定:有时候会直接死锁,有时候跑到一半触发段错误,偶尔又能完全正常执行。我自认为互斥锁和条件变量的用法没问题,但实在找不到问题根源,想请各位帮忙排查一下。
先贴出我的代码:
#include <iostream> #include <stack> #include <time.h> #include <mutex> #include <condition_variable> #include <random> #include <thread> #include <windows.h> #include <algorithm> #include <stack> #define MAX 100 #define MIN 0 std::condition_variable cv; std::mutex mtx; class ProducerConsumer{ public: // 创建存储数据的栈 std::stack<int> stack_array; // 统计生产/消费的总和 int producer_sum = 0; int consumer_sum = 0; // 生成随机数 int random_number_generator(){ std::random_device rd; std::default_random_engine re{rd()}; return re(); } // 生产者:生成数据并压入栈 void producer(){ for (int i = 0 ; i < MAX ; i++){ std::unique_lock<std::mutex> lg(mtx); if (stack_array.size() == MAX){ std::cout << "Producer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); } int rand_num = random_number_generator()%10+1; int rand_num_sleep = random_number_generator()%100+1; std::cout << "Current Stack Size - " << stack_array.size() << " | Pushing: " << rand_num << "... " << "and sleeping for: " << rand_num_sleep << " milliseconds" << std::endl; producer_sum += rand_num; stack_array.push(rand_num); std::this_thread::sleep_for(std::chrono::milliseconds(rand_num_sleep)); cv.notify_all(); lg.unlock(); } } // 消费者:从栈中取出数据 void consumer(){ for (int i = 0 ; i < MAX ; i++) { std::unique_lock<std::mutex> lg(mtx); if (stack_array.size() == MIN){ std::cout << "Consumer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); } int rand_num_sleep = random_number_generator()%100+1; std::cout << "Current Stack Size - " << stack_array.size() << " | Popping: " << stack_array.top() << "... " << "and sleeping for: " << rand_num_sleep << " milliseconds" << std::endl; consumer_sum += stack_array.top(); stack_array.pop(); std::this_thread::sleep_for(std::chrono::milliseconds(rand_num_sleep)); cv.notify_all(); lg.unlock(); } } }; int main(){ ProducerConsumer pc; std::thread producer_one (&ProducerConsumer::producer, &pc); std::thread consumer_one (&ProducerConsumer::consumer, &pc); std::thread producer_two (&ProducerConsumer::producer, &pc); std::thread consumer_two (&ProducerConsumer::consumer, &pc); producer_one.join(); producer_two.join(); consumer_one.join(); consumer_two.join(); std::cout << "Producer sum: " << pc.producer_sum << std::endl; std::cout << "Consumer sum: " << pc.consumer_sum << std::endl; std::cout << "Stack size: " << pc.stack_array.size() << std::endl; }
运行状况说明
我启动了2个生产者线程和2个消费者线程,每个生产者要生产100个数据,每个消费者要消费100个。但实际运行时:
- 偶尔程序刚启动就死锁,卡在生产者/消费者等待的状态
- 有时运行到一半突然死锁,或者触发段错误(推测是访问了空栈的
top()) - 少数情况下能正常跑完,生产者和消费者的总和一致,栈最终为空
我怀疑是条件变量的等待逻辑有问题,但不确定具体哪里错了,麻烦大家帮忙看看,谢谢!
问题排查与修正建议
我帮你梳理了几个核心问题,这些正是导致死锁和段错误的根源:
1. 条件变量等待用if而非while,引发虚假唤醒后的非法操作
这是条件变量最常见的误用!线程调用cv.wait(lg)时,可能会发生虚假唤醒(不是被notify_all触发,而是系统自发唤醒),如果只用if判断栈的状态,线程会直接继续执行——比如消费者被虚假唤醒后,栈其实还是空的,这时候调用stack_array.top()就会触发段错误。
正确的做法是用while循环重复检查条件:
// 生产者的等待逻辑修改为: while (stack_array.size() == MAX) { std::cout << "Producer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); } // 消费者的等待逻辑修改为: while (stack_array.size() == MIN) { std::cout << "Consumer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); }
wait每次唤醒后都会重新检查条件,确保栈的状态确实符合执行要求,彻底避免虚假唤醒带来的风险。
2. 持有互斥锁时执行耗时睡眠,加剧线程阻塞
你在持有mtx锁的状态下调用std::this_thread::sleep_for,这会让其他线程长时间无法获取锁,大幅增加死锁概率,同时降低并发效率。
应该把睡眠操作移到解锁之后:
// 生产者修正示例: std::unique_lock<std::mutex> lg(mtx); while (stack_array.size() == MAX) { std::cout << "Producer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); } int rand_num = random_number_generator()%10+1; int rand_num_sleep = random_number_generator()%100+1; std::cout << "Current Stack Size - " << stack_array.size() << " | Pushing: " << rand_num << "... " << "and sleeping for: " << rand_num_sleep << " milliseconds" << std::endl; producer_sum += rand_num; stack_array.push(rand_num); cv.notify_all(); lg.unlock(); // 先释放锁 std::this_thread::sleep_for(std::chrono::milliseconds(rand_num_sleep)); // 再执行睡眠 // 消费者修正示例: std::unique_lock<std::mutex> lg(mtx); while (stack_array.size() == MIN) { std::cout << "Consumer Thread " << std::this_thread::get_id() << " is waiting" << std::endl; cv.wait(lg); } int rand_num_sleep = random_number_generator()%100+1; int top_val = stack_array.top(); // 先取出栈顶值,避免后续操作出错 std::cout << "Current Stack Size - " << stack_array.size() << " | Popping: " << top_val << "... " << "and sleeping for: " << rand_num_sleep << " milliseconds" << std::endl; consumer_sum += top_val; stack_array.pop(); cv.notify_all(); lg.unlock(); // 释放锁后再睡眠 std::this_thread::sleep_for(std::chrono::milliseconds(rand_num_sleep));
这样锁的持有时间只保留在操作共享资源(栈、求和变量)的必要阶段,其他耗时操作不占用锁,线程间的阻塞会大幅减少。
3. 随机数生成器重复初始化(非致命但建议优化)
你每次调用random_number_generator()都重新创建std::random_device和std::default_random_engine,不仅效率低,还可能导致随机数分布不均匀。可以把生成器作为类成员初始化一次:
class ProducerConsumer{ public: // ... 其他成员 ... std::random_device rd; std::default_random_engine re; // 构造函数初始化生成器 ProducerConsumer() : re(rd()) {} // 生成随机数 int random_number_generator(){ return re(); } // ... 其他方法 ... };
4. 重复包含头文件
代码里重复包含了<stack>,可以删掉其中一个,让代码更整洁。
把这些问题修正后,程序应该就能稳定运行,不会再出现死锁和段错误了。你可以先优先修改if为while这一步,应该能解决大部分核心问题。
备注:内容来源于stack exchange,提问作者jfelix-agda

