Linux下用fork()派生的子进程通过pipe管道通讯的实例详解("生产者-消费者"问题)
//基于fork()系统调用
//#include "sys/types.h"//包含pid_t等的声明
//#include "sys/file.h"
//#include "unistd.h"//包含pipe(),fork()等的声明
/*在一般的较新的linux系统(如Ubuntu)中,以上头文件的引入不是必须的,用gcc编译时不会报error*/
/*如果出现关于pid_t的错误,可以包含以上头文件中任意一个,或者包含stdlib.h*/
/*如果出现类似于“不兼容的隐式声明”或“incompatible implicit declaration of built-in function ***”的警告(warning),包含以下三个头文件即可消除警告*/
#include "stdlib.h"//包含exit(),pid_t等的声明
#include "string.h"//包含strcpy()等的声明
#include "stdio.h"//包含printf()等的声明
char r_buf[4];//管道读取缓冲
char w_buf[4];//管道写入缓冲
int pipe_fd[2];//管道读写文件指针
pid_t pid1,pid2,pid3,pid4;//用于保存子进程号,此程序中并没有用到,只有一个简单的赋值
int producer(int id);//“生产者”
int consumer(int id);//“消费者”
int main(int argc,char **argv)
{
//pipe()成功返回,失败返回-1
if(pipe(pipe_fd)<0){//文件指针fd[0]和fd[1]分别用于管道文件的读和写
printf("Pipe create error.\n");
exit(-1);
}
else{
printf("Pipe is created successfully !\n");
if((pid1=fork())==0)
producer(1);//对于fork()得到的子进程,pid1=0,于是调用一个生产者,以下三句类似
if((pid2=fork())==0)
producer(2);
if((pid3=fork())==0)
consumer(1);
if((pid4=fork())==0)
consumer(2);
}
close(pipe_fd[0]);//去掉之后,对程序结果没有影响
close(pipe_fd[1]);//需要加上这句,否则会有读者永远等待
int i,pid,status;
/*等待四个子进程结束,否则主程序先于子程序结束(但是子进程仍在进行,对结果无影响)*/
/*对于每个子进程,此处返回的pid对应于上面的进程号pid1,pid2,pid3和pid4,而status则对应于子进程结束时发送给系统的返回值*/
/*注意返回值的低半字为零,高半字为exit(int ret)中指定的值;简单的说,就是256*ret */
for(i=0;i<4;i++)
pid = wait(&status);
//exit()函数要做的工作是退出处理函数(我认为,相当于return),然后清理I/O缓冲,最后调用exit系统调用
exit(0);//main函数中,exit(int ret)等同于return ret;
}
int producer(int id){
printf("Producer %d is running !\n",id);
close(pipe_fd[0]);//关读
int i = 0;
for(i=1;i<10;i++){
sleep(3);
if(id == 1)//生产者
strcpy(w_buf,"aaa\0");
else//生产者
strcpy(w_buf,"bbb\0");
if(write(pipe_fd[1],w_buf,4) == -1)//写管道
printf("Write to pipe error\n");
}
close(pipe_fd[1]);//关写
printf("Producer %d is over !\n",id);
/*结束(子)进程,并给系统(父进程)一个返回值;不可以用return,因为return只是返回函数调用处*/
exit(id);
}
int consumer(int id){
close(pipe_fd[1]);//关写
printf("Consumer %d is running !\n",id);
if(id == 1)//消费者
strcpy(w_buf,"ccc\0");
else//消费者
strcpy(w_buf,"ddd\0");
while(1){
sleep(1);//一个消费者读取管道后暂时挂起,给另一个消费者运行的机会
strcpy(r_buf,"eee\0");
//read()返回值为0表示到了文件结尾;返回-1表示读取过程有错误;一般情况返回读取的字节总数
if(read(pipe_fd[0],r_buf,4) == 0)//如果写入端尚未写入数据,则读操作被阻塞,陷入等待,不能返回;直至写文件指针被关闭
break;
printf("Consumer %d get %s,while the w_buf is %s\n",id,r_buf,w_buf);
}
close(pipe_fd[0]);
printf("Consumer %d is over !\n",id);
exit(id);
}
以上代码中,关闭读文件操作符pipe_fd[0]的操作不是必须的,去掉之后,不会影响程序的运行结果,但是为了管道操作的规范性,需要加上close(pipe_fd[0])的操作
由于只有当read函数读到管道文件的结尾(写文件操作结束,read函数返回0)时,消费者进程consumer才能退出死循环,并结束,因此如果少了关闭写文件操作符的close(pipe_fd[1])操作,就会导致有消费者进程永远等待,无法返回。