基于Semaphore、Mutex与PThread的多线程生产者/消费者问题求助
解决Semaphore+Mutex实现生产者/消费者的两大常见问题
嘿,我当年做操作系统课程的多线程作业时,也在Semaphore+Mutex实现生产者消费者这部分踩过一模一样的坑!咱们来逐个拆解你的问题,再给你能直接跑的修复方案~
问题1:先启动消费者时读取无效字符崩溃
问题根源
这基本是因为消费者在缓冲区完全为空的时候,跳过了“等待数据”的步骤就直接访问内存了!常见的错误场景有两种:
- 数据信号量(标记缓冲区有多少可用数据)的初始值没设为0,比如误设成1,导致消费者一开始就认为有数据,去读空缓冲区的垃圾内存;
- 操作顺序搞反了:消费者先拿互斥锁(
pthread_mutex_lock)再等数据信号量(sem_wait),但如果缓冲区为空,消费者拿着互斥锁阻塞,生产者根本拿不到锁去生产,最后要么死锁,要么消费者硬读空内存崩溃。
修复方案
- 把数据信号量(
sem_data)初始值设为0,确保消费者必须等生产者生产了数据才能继续; - 消费者的操作顺序必须是:先等数据信号量 → 再拿互斥锁 → 读缓冲区 → 释放互斥锁 → 通知生产者有空位。
问题2:先启动生产者时无法完成生产
问题根源
这个通常是空位信号量(标记缓冲区有多少空位)的处理顺序错了,或者初始值设错了:
- 空位信号量初始值没设成缓冲区大小,比如设成0,生产者一开始就认为缓冲区满了,直接阻塞住;
- 生产者先拿互斥锁再等空位信号量,当缓冲区满时,生产者拿着互斥锁阻塞,消费者根本拿不到锁去消费,空位信号量永远不会被唤醒,生产者就卡着没法继续生产。
修复方案
- 把空位信号量(
sem_empty)初始值设为缓冲区的大小; - 生产者的操作顺序必须是:先等空位信号量 → 再拿互斥锁 → 写缓冲区 → 释放互斥锁 → 通知消费者有新数据;
- 绝对别在持有互斥锁的情况下调用
sem_wait,这是死锁的重灾区!
修复后的完整代码示例
#include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> #include <unistd.h> #define BUFFER_SIZE 5 // 缓冲区大小 #define ITEM_COUNT 10 // 总共生产/消费的物品数量 char buffer[BUFFER_SIZE]; int in = 0; // 生产者写入位置 int out = 0; // 消费者读取位置 sem_t sem_empty; // 标记缓冲区空位数量,初始为BUFFER_SIZE sem_t sem_data; // 标记缓冲区数据数量,初始为0 pthread_mutex_t mutex; // 生产者线程函数 void* producer(void* arg) { for (int i = 0; i < ITEM_COUNT; i++) { char item = 'A' + i; // 生产A-J的字符 // 第一步:等待缓冲区有空位 sem_wait(&sem_empty); // 第二步:加锁,确保同一时间只有一个线程操作缓冲区 pthread_mutex_lock(&mutex); // 写入缓冲区 buffer[in] = item; printf("生产者生产: %c,写入位置: %d\n", item, in); in = (in + 1) % BUFFER_SIZE; // 第三步:解锁,释放缓冲区的访问权 pthread_mutex_unlock(&mutex); // 第四步:通知消费者有新数据可用 sem_post(&sem_data); sleep(rand() % 2); // 模拟生产耗时,随机0-1秒 } pthread_exit(NULL); } // 消费者线程函数 void* consumer(void* arg) { for (int i = 0; i < ITEM_COUNT; i++) { char item; // 第一步:等待缓冲区有数据 sem_wait(&sem_data); // 第二步:加锁访问缓冲区 pthread_mutex_lock(&mutex); // 读取缓冲区 item = buffer[out]; printf("消费者消费: %c,读取位置: %d\n", item, out); out = (out + 1) % BUFFER_SIZE; // 第三步:解锁 pthread_mutex_unlock(&mutex); // 第四步:通知生产者有空位了 sem_post(&sem_empty); sleep(rand() % 2); // 模拟消费耗时 } pthread_exit(NULL); } int main() { pthread_t prod_tid, cons_tid; // 初始化信号量和互斥锁 sem_init(&sem_empty, 0, BUFFER_SIZE); sem_init(&sem_data, 0, 0); pthread_mutex_init(&mutex, NULL); // 测试先启动消费者的场景(取消注释即可测试先启动生产者) pthread_create(&cons_tid, NULL, consumer, NULL); sleep(1); // 确保消费者先启动 pthread_create(&prod_tid, NULL, producer, NULL); // 测试先启动生产者的场景 // pthread_create(&prod_tid, NULL, producer, NULL); // sleep(1); // pthread_create(&cons_tid, NULL, consumer, NULL); // 等待线程结束 pthread_join(prod_tid, NULL); pthread_join(cons_tid, NULL); // 销毁资源 sem_destroy(&sem_empty); sem_destroy(&sem_data); pthread_mutex_destroy(&mutex); return 0; }
关键注意事项
- 信号量初始化是核心:
sem_empty初始为缓冲区大小,sem_data初始为0,这是Semaphore实现生产者消费者的基础规则; - 操作顺序不能乱:信号量的等待/通知必须在互斥锁的外面,锁的范围只保护缓冲区的读写操作,这样才能避免死锁;
- 测试场景要覆盖:分别测试先启动生产者、先启动消费者的情况,确保两种场景都能正常运行。
内容的提问来源于stack exchange,提问作者ucagent
相关产品推荐
相关产品推荐

