【进程与线程】Linux文件 I/O 编程中的生产者与消费者案例
生产者与消费者问题是经典的多线程同步问题,通常用于描述两个进程之间共享资源的协作。在 Linux 文件 I/O 中,我们这里使用有名管道(FIFO) 来实现生产者与消费者模型。
有名管道是一个特殊的文件,可以在两个不相关的进程之间传递数据(非亲缘进程)。它的特点是:
- 数据以先进先出的顺序传递。
- 数据在被读取后从管道中移除。
实现步骤
- 创建有名管道:使用
mkfifo()
创建一个有名管道文件。 - 生产者进程:打开有名管道并向其中写入数据。
- 消费者进程:打开有名管道并从其中读取数据。
- 同步与阻塞:
- 有名管道是阻塞的:
- 如果生产者没有写入数据,消费者会阻塞等待。
- 如果消费者没有读取数据,生产者会阻塞等待。
- 有名管道是阻塞的:
代码实现
C语言实现,分为生产者和消费者两个独立的进程:
头文件与常量
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#define FIFO_FILE "myfifo" // 定义有名管道的文件名
#define BUFFER_SIZE 256 // 定义缓冲区大小
生产者程序
生产者负责生成数据并写入管道。
void producer() {
int fd;
char buffer[BUFFER_SIZE];
int count = 0;
// 打开有名管道(写入模式)
fd = open(FIFO_FILE, O_WRONLY);
if (fd == -1) {
perror("Producer: Failed to open FIFO");
exit(EXIT_FAILURE);
}
printf("Producer: Writing to FIFO...\n");
// 模拟生产数据
while (1) {
snprintf(buffer, BUFFER_SIZE, "Message %d", count);
write(fd, buffer, strlen(buffer) + 1); // 写入数据到管道
printf("Producer: Sent '%s'\n", buffer);
count++;
sleep(1); // 模拟生产数据所需的时间
}
close(fd); // 关闭管道
}
消费者程序
消费者负责从管道中读取数据。
void consumer() {
int fd;
char buffer[BUFFER_SIZE];
// 打开有名管道(读取模式)
fd = open(FIFO_FILE, O_RDONLY);
if (fd == -1) {
perror("Consumer: Failed to open FIFO");
exit(EXIT_FAILURE);
}
printf("Consumer: Reading from FIFO...\n");
// 模拟消费数据
while (1) {
memset(buffer, 0, BUFFER_SIZE); // 清空缓冲区
int bytes = read(fd, buffer, BUFFER_SIZE); // 从管道中读取数据
if (bytes > 0) {
printf("Consumer: Received '%s'\n", buffer);
} else if (bytes == 0) {
// 管道写入端关闭时,read 返回 0
printf("Consumer: No more data to read. Exiting...\n");
break;
} else {
perror("Consumer: Read error");
exit(EXIT_FAILURE);
}
}
close(fd); // 关闭管道
}
主程序
主程序创建管道并选择运行生产者或消费者。
int main(int argc, char *argv[]) {
// 创建有名管道
if (mkfifo(FIFO_FILE, 0666) == -1) {
if (errno != EEXIST) {
perror("Failed to create FIFO");
exit(EXIT_FAILURE);
}
}
if (argc < 2) {
fprintf(stderr, "Usage: %s producer|consumer\n", argv[0]);
exit(EXIT_FAILURE);
}
if (strcmp(argv[1], "producer") == 0) {
producer(); // 启动生产者
} else if (strcmp(argv[1], "consumer") == 0) {
consumer(); // 启动消费者
} else {
fprintf(stderr, "Invalid argument. Use 'producer' or 'consumer'.\n");
exit(EXIT_FAILURE);
}
return 0;
}
运行步骤:
- 编译程序:
gcc -o fifo_example fifo_example.c
- 运行消费者:
- 在一个终端中启动消费者:
./fifo_example consumer
- 运行生产者:
./fifo_example producer
- 观察输出:
- 消费者终端将实时显示从管道中读取的数据。
- 生产者终端会显示发送的数据。
示例输出
生产者终端:
Producer: Writing to FIFO...
Producer: Sent 'Message 0'
Producer: Sent 'Message 1'
Producer: Sent 'Message 2'
...
消费者终端:
Consumer: Reading from FIFO...
Consumer: Received 'Message 0'
Consumer: Received 'Message 1'
Consumer: Received 'Message 2'
...
注意事项:
- 管道阻塞:
- 如果生产者启动后没有消费者读取,生产者会阻塞,直到消费者启动并读取数据。
- 同样,如果消费者启动后没有生产者写入,消费者会阻塞,直到生产者写入数据。
- 管道的清理:
- 运行结束后,删除管道文件:
rm myfifo
- 并发问题:
- 这个示例中只有一个生产者和一个消费者。如果需要支持多个生产者和消费者,可以使用信号量或文件锁机制进行同步。
这篇文章通过生产者和消费者模型展示了使用有名管道传递数据的基本方法。有点在于易于实现,适合简单的进程间通信;缺点是仅支持单向通信。阻塞模式需要注意处理多个进程的同步问题。
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!