摘要
本文记录了在 Ubuntu 虚拟机环境下,使用 C 语言和 POSIX 线程库解决经典“生产者-消费者”问题的完整代码与编译步骤。核心在于利用信号量控制缓冲区同步,利用互斥锁保证数据安全。
1. 核心思路
- 互斥锁 (
mutex):保证同一时刻只有一个线程操作缓冲区。 - 信号量 (
empty,full):empty:初始为缓冲区大小 $M$,表示还能放多少数据。full:初始为 0,表示已经有多少数据可取。
2. 完整代码 (producer_consumer.c)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 5 // 缓冲区大小 #define PRODUCER_COUNT 2 #define CONSUMER_COUNT 2 int buffer[BUFFER_SIZE]; int in = 0, 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); // 加锁 // --- 临界区:放入数据 --- buffer[in] = 1; printf("[生产者 %d] 放入数据 -> 位置: %d\n", id, in); in = (in + 1) % BUFFER_SIZE; pthread_mutex_unlock(&mutex); // 解锁 sem_post(&full_sem); // V(full): 增加满位 } } void *consumer(void *arg) { int id = *(int *)arg; while(1) { sleep(2); // 模拟消费耗时 sem_wait(&full_sem); // P(full): 等待数据 pthread_mutex_lock(&mutex); // 加锁 // --- 临界区:取出数据 --- int data = buffer[out]; printf("[消费者 %d] 取出数据 <- 位置: %d\n", id, out); out = (out + 1) % BUFFER_SIZE; pthread_mutex_unlock(&mutex); // 解锁 sem_post(&empty_sem); // V(empty): 增加空位 } } int main() { pthread_t prod[PRODUCER_COUNT], cons[CONSUMER_COUNT]; int p_ids[PRODUCER_COUNT], c_ids[CONSUMER_COUNT]; // 初始化 sem_init(&empty_sem, 0, BUFFER_SIZE); sem_init(&full_sem, 0, 0); pthread_mutex_init(&mutex, NULL); // 创建线程 for(int i=0; i<PRODUCER_COUNT; i++) { p_ids[i] = i+1; pthread_create(&prod[i], NULL, producer, &p_ids[i]); } for(int i=0; i<CONSUMER_COUNT; i++) { c_ids[i] = i+1; pthread_create(&cons[i], NULL, consumer, &c_ids[i]); } // 等待结束 (此处为死循环演示,实际需Ctrl+C退出) for(int i=0; i<PRODUCER_COUNT; i++) pthread_join(prod[i], NULL); for(int i=0; i<CONSUMER_COUNT; i++) pthread_join(cons[i], NULL); return 0; }3. 编译与运行步骤
在你的 Ubuntu 终端中执行以下命令:
- 编译(注意必须链接
pthread库):gcc producer_consumer.c -o pc_demo -lpthread - 运行:
./pc_demo
4. 实验总结
通过观察控制台输出,可以看到生产者和消费者交替进行,且不会出现“缓冲区溢出”或“从空缓冲区读取”的错误,验证了 PV 操作的有效性。