条件变量正确使用方法及生产者消费者模型无输出问题排查
条件变量正确用法及你的代码问题修复
条件变量核心使用原则
- 必须与互斥锁绑定使用:调用
pthread_cond_wait前必须持有锁,函数执行时会自动释放锁,被唤醒后会重新获取锁,无需手动解锁。 - 用while循环检查等待条件:防止虚假唤醒(系统可能无理由唤醒线程),或者唤醒后条件已不满足(比如多消费者场景下其他线程已处理了数据)。
- 信号要发给对应等待的线程:生产者生产完后,发信号通知消费者“有数据了”;消费者消费完后,发信号通知生产者“有空间了”。
你的代码里的关键错误
- 条件变量完全用反了:你让生产者等待自己的
cond1、消费者等待自己的cond2,相当于自己等自己发信号,完全起不到线程间同步的作用。 pthread_cond_wait后手动解锁:pthread_cond_wait返回时已经重新持有了互斥锁,此时再解锁会导致重复解锁,引发线程库状态混乱,直接导致死锁。- 用if判断等待条件:没有处理虚假唤醒,且多线程场景下条件可能已变化,必须用while循环重新检查。
- 外层轮询循环多余:条件变量的作用就是避免轮询,你保留的外层while轮询完全没必要,还会逻辑混乱。
- 信号发送时机错误:你在进入临界区时就发信号,而不是在修改完共享数据后发,导致信号无效。
- 未初始化共享变量:
shared_count没有初始化为0,初始值不确定会直接破坏条件判断逻辑。
修正后的完整代码
/* minor3.c - using producer-consumer paradigm. */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define NITEMS 10 // number of items in shared buffer // shared variables char shared_buffer[NITEMS]; // echo buffer int shared_count = 0; // 必须初始化,否则初始值不确定 pthread_mutex_t mutex; // pthread mutex unsigned int prod_index = 0; // producer index into shared buffer unsigned int cons_index = 0; // consumer index into shard buffer // function prototypes void * producer(void *arg); void * consumer(void *arg); // 明确条件变量作用:通知消费者有数据、通知生产者有空间 pthread_cond_t cond_not_empty = PTHREAD_COND_INITIALIZER; pthread_cond_t cond_not_full = PTHREAD_COND_INITIALIZER; int main() { pthread_t prod_tid, cons_tid1, cons_tid2; // initialize pthread variables pthread_mutex_init(&mutex, NULL); // initialize condition variables pthread_cond_init(&cond_not_empty, NULL); pthread_cond_init(&cond_not_full, NULL); // start producer thread pthread_create(&prod_tid, NULL, producer, NULL); // start consumer threads pthread_create(&cons_tid1, NULL, consumer, NULL); pthread_create(&cons_tid2, NULL, consumer, NULL); // wait for threads to finish pthread_join(prod_tid, NULL); pthread_join(cons_tid1, NULL); pthread_join(cons_tid2, NULL); // clean up pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond_not_empty); pthread_cond_destroy(&cond_not_full); return 0; } // producer thread executes this function void * producer(void *arg) { char key; printf("Enter text for producer to read and consumer to print, use Ctrl-C to exit.\n\n"); while (1) { // scanf会阻塞等待输入,这部分无需加锁 scanf("%c", &key); // 获取锁后操作共享资源 pthread_mutex_lock(&mutex); // 用while循环等待缓冲区非满,处理虚假唤醒 while (shared_count == NITEMS) { // 等待消费者发的"有空间"信号 pthread_cond_wait(&cond_not_full, &mutex); } // 写入数据到缓冲区 shared_buffer[prod_index] = key; shared_count++; // 更新生产者索引 prod_index = (prod_index == NITEMS - 1) ? 0 : prod_index + 1; // 通知消费者:缓冲区有数据了 pthread_cond_signal(&cond_not_empty); // 释放锁 pthread_mutex_unlock(&mutex); } return NULL; } // consumer thread executes this function void * consumer(void *arg) { char key; long unsigned int id = (long unsigned int)pthread_self(); while (1) { pthread_mutex_lock(&mutex); // 用while循环等待缓冲区非空,处理虚假唤醒 while (shared_count == 0) { // 等待生产者发的"有数据"信号 pthread_cond_wait(&cond_not_empty, &mutex); } // 从缓冲区读取数据 key = shared_buffer[cons_index]; printf("consumer %lu: %c\n", id, key); shared_count--; // 更新消费者索引 cons_index = (cons_index == NITEMS - 1) ? 0 : cons_index + 1; // 通知生产者:缓冲区有空间了 pthread_cond_signal(&cond_not_full); // 释放锁 pthread_mutex_unlock(&mutex); } return NULL; }
关键修改说明
- 初始化
shared_count:明确初始值为0,保证条件判断的正确性。 - 重命名条件变量:用
cond_not_empty和cond_not_full明确分工,避免混淆。 - 移除多余轮询循环:直接在持有锁的状态下用while循环等待条件,彻底消除轮询导致的高CPU占用。
- 修正
wait后的解锁逻辑:去掉pthread_cond_wait后的手动解锁,避免重复解锁引发的死锁。 - 调整信号发送时机:在修改完共享数据后发送信号,确保信号能准确传递有效状态。
- 用while循环检查条件:处理虚假唤醒和多线程竞争场景下的条件变化。
内容的提问来源于stack exchange,提问作者Samuel Afon
相关产品推荐
相关产品推荐

