
摘要本文记录了在 Ubuntu Linux 环境下使用 GCC 和 POSIX 线程库pthread解决经典并发编程问题——“生产者 - 消费者问题”的全过程。文章详细分析了信号量Semaphore与互斥锁Mutex的配合机制并提供了完整的可运行代码及编译调试步骤。对于正在学习操作系统课程的同学这是一份实用的参考指南。一、前言最近在进行操作系统的课程设计核心任务是理解进程 / 线程间的同步与互斥机制。经典的“生产者 - 消费者问题”是理解这一概念的基石。我的开发环境是在 Windows 宿主机上通过VMware Workstation运行的Ubuntu 64 位虚拟机。这种环境既能保证系统的安全性又能提供原汁原味的 Linux 命令行体验非常适合进行底层系统编程的学习。二、问题分析在这个实验中我们需要模拟多个生产者和多个消费者共享一个固定大小的缓冲区生产者Producer负责生产数据并放入缓冲区。如果缓冲区满了必须等待。消费者Consumer负责从缓冲区取出数据。如果缓冲区空了必须等待。互斥访问无论生产还是消费同一时刻只能有一个线程操作缓冲区以防止数据错乱。为了实现上述逻辑我们需要三个关键的同步工具mutex互斥锁保护缓冲区的互斥访问。empty信号量记录空闲缓冲区的数量初始值为缓冲区总大小 $M$。full信号量记录已填充数据的缓冲区数量初始值为 $0$。三、核心代码实现以下是我在 Ubuntu 终端中编写并验证通过的完整代码producer_consumer.c。代码使用了semaphore.h和pthread.h库。#include stdio.h #include stdlib.h #include unistd.h #include pthread.h #include semaphore.h #define BUFFER_SIZE 5 // 缓冲区大小 M #define PRODUCER_NUM 2 // 生产者数量 #define CONSUMER_NUM 2 // 消费者数量 int buffer[BUFFER_SIZE]; int in 0; // 生产者写入位置 int out 0; // 消费者读取位置 // 定义信号量和互斥锁 sem_t empty_sem; sem_t full_sem; pthread_mutex_t mutex; // 生产者函数 void *producer(void *arg) { int id *(int *)arg; while (1) { sleep(1); // 模拟生产耗时 sem_wait(empty_sem); // P(empty): 申请空位 pthread_mutex_lock(mutex); // Lock: 进入临界区 // --- 写入数据 --- buffer[in] 1; printf([生产者 %d] 放入数据到位置 %d\n, id, in); in (in 1) % BUFFER_SIZE; pthread_mutex_unlock(mutex); // Unlock: 离开临界区 sem_post(full_sem); // V(full): 增加满位计数 } return NULL; } // 消费者函数 void *consumer(void *arg) { int id *(int *)arg; while (1) { sleep(2); // 模拟消费耗时 sem_wait(full_sem); // P(full): 申请数据 pthread_mutex_lock(mutex); // Lock: 进入临界区 // --- 读取数据 --- printf([消费者 %d] 从位置 %d 取出数据\n, id, out); buffer[out] 0; out (out 1) % BUFFER_SIZE; pthread_mutex_unlock(mutex); // Unlock: 离开临界区 sem_post(empty_sem); // V(empty): 增加空位计数 } return NULL; } int main() { pthread_t prod_threads[PRODUCER_NUM], cons_threads[CONSUMER_NUM]; int prod_ids[PRODUCER_NUM], cons_ids[CONSUMER_NUM]; // 初始化 sem_init(empty_sem, 0, BUFFER_SIZE); sem_init(full_sem, 0, 0); pthread_mutex_init(mutex, NULL); // 创建线程 for (int i 0; i PRODUCER_NUM; i) { prod_ids[i] i; pthread_create(prod_threads[i], NULL, producer, prod_ids[i]); } for (int i 0; i CONSUMER_NUM; i) { cons_ids[i] i; pthread_create(cons_threads[i], NULL, consumer, cons_ids[i]); } // 等待线程结束此处为死循环演示实际需配合退出逻辑 for (int i 0; i PRODUCER_NUM; i) pthread_join(prod_threads[i], NULL); for (int i 0; i CONSUMER_NUM; i) pthread_join(cons_threads[i], NULL); // 销毁资源 sem_destroy(empty_sem); sem_destroy(full_sem); pthread_mutex_destroy(mutex); return 0; }四、编译与运行指南在 Ubuntu 虚拟机中请按照以下步骤操作保存文件将上述代码保存为producer_consumer.c。编译命令注意需要链接pthread库。gcc producer_consumer.c -o pc_demo -lpthread运行程序./pc_demo观察输出你会看到生产者和消费者交替打印日志且不会出现缓冲区溢出或读取空数据的情况。按CtrlC终止程序。五、实验心得与注意事项P/V 操作顺序至关重要在生产者中必须先wait(empty)再lock(mutex)在消费者中必须先wait(full)再lock(mutex)。如果顺序反了先锁后等信号量极易导致死锁。编译报错处理如果在 Ubuntu 上编译提示找不到semaphore.h请确保安装了libc6-dev包通常默认已安装。虚拟机的优势如果在实验中不小心写错了代码导致系统卡死直接关闭 VMware 虚拟机重启即可完全不会影响宿主机的 Windows 系统这大大降低了试错成本。