You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程生产者/消费者C++程序随机死锁与段错误问题排查求助

多线程生产者/消费者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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.23 13:57:33