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

基于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;
}

关键注意事项

  1. 信号量初始化是核心:sem_empty初始为缓冲区大小,sem_data初始为0,这是Semaphore实现生产者消费者的基础规则;
  2. 操作顺序不能乱:信号量的等待/通知必须在互斥锁的外面,锁的范围只保护缓冲区的读写操作,这样才能避免死锁;
  3. 测试场景要覆盖:分别测试先启动生产者、先启动消费者的情况,确保两种场景都能正常运行。

内容的提问来源于stack exchange,提问作者ucagent

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 11:14:37