一个进程中的多个线程共享一个进程的堆等内存空间,因此实现数据交互是很方便的;但多进程架构中,要想实现多进程间的数据交互相对就困难不少!web
进程间通讯(IPC,InterProcess Communication)是指在不一样进程之间传递或交换信息。IPC常见的方式有:管道(无名管道、命名管道)、消息队列、信号量、共享内存、磁盘文件、Socket等,其中Socket网络方式能够实现不一样主机上的多进程IPC小程序
下面使用fork()、pipe()实现一个简单的Linux平台下的多进程、多进程通讯的程序数组
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main() { int pipefd[2]; //两个文件描述符 pid_t pid; char buff[20]; int i; //建立管道 if(0 > pipe(pipefd)){ printf("Create Pipe Error!\n"); } //建立2个子进程 for(i=0; i<2; i++){ pid = fork(); if((pid_t)0 >= pid){ sleep(1); break; } } //若是fork返回值小于0,说明建立子进程失败 if((pid_t)0 > pid){ printf("Fork Error!\n"); return 3; } //若是fork返回值为0,表示进入子进程的逻辑 if((pid_t)0 == pid){ //发送格式化数据给主进程 FILE *f; f = fdopen(pipefd[1], "w"); fprintf(f, "I am %d\n", getpid()); fclose(f); sleep(1); //接收父进程发过来的数据 read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); } //进入父进程的逻辑 else{ //循环接收全部子进程发过来的数据,而且返回数据给子进程 for(i=0; i<2; i++){ //接收子进程发来的数据 read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); //发送数据给子进程 write(pipefd[1], "Hello My Son\n", 14); } //这里调用sleep(3)主要的做用是等待子进程运行结束 //固然这样并非很规范! sleep(3); } return 0; }
编译程序gcc process.c -o process
,而后执行./process
输入信息以下安全
但咱们能够发现输出的内容有一些异常,好比第二行最开始怎么有一个@字符、最后一行明显丢失了一些字符信息等网络
上面的程序还不止是输出不符合预期这个表面的问题,还存在诸多的坑,都是由于一开始对于多进程、管道的深刻机制理解不正确形成的!架构
下面针对Linux的管道进行比较深刻的挖掘,就能够发现上面的小程序中存在不少的坑并发
管道读写是阻塞的,当管道中没有数据,但进程尝试去读的时候就会阻塞进程,好比socket
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main() { int pipefd[2]; pid_t pid; char buff[20]; int i; if(0 > pipe(pipefd)){ printf("Create Pipe Error!\n"); } pid = fork(); if((pid_t)0 > pid){ printf("Fork Error!\n"); return 3; } if((pid_t)0 == pid){ //write(pipefd[1], "Hello\n", 6); } else{ read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); } return 0; }
其运行效果以下,能够看到主进程阻塞住了spa
能够修改让子进程往管道中写入数据,主进程再去读,这样就不会阻塞了线程
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main() { int pipefd[2]; pid_t pid; char buff[20]; int i; if(0 > pipe(pipefd)){ printf("Create Pipe Error!\n"); } pid = fork(); if((pid_t)0 > pid){ printf("Fork Error!\n"); return 3; } if((pid_t)0 == pid){ } else{ read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); } return 0; }
运行程序能够看到主进程没有阻塞
所谓半双工,意思就是只能在一个方向上传递数据,对于pipe,只能从pipe[1]写,从pipe[0]读,只能在一个方向传递数据;能够结合socket来理解,socket是全双工的,也就是针对一个socket既能够写又能够读
第一个例程中建立了一个管道,但却但愿经过这个管道实现主进程传递数据给子进程、子进程传递数据给主进程,彻底是想在两个方向传递数据,结果致使主进程和2个子进程同时既往管道里写,又从管道里读,因此出现了上述诡异的现象
好比下面这个例子,建立一个管道,但不建立子进程,能够在主进程既写又读管道!
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main() { int pipefd[2]; pid_t pid; char buff[20]; int i; if(0 > pipe(pipefd)){ printf("Create Pipe Error!\n"); } write(pipefd[1], "Hello\n", 6); read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); return 0; }
由于管道是半双工的,因此要想在保证数据不乱的状况下,不能在多进程应用中只使用一个管道,须要一套管道,有的是数据从主进程到子进程,有的是数据从子进程到主进程
#include <stdio.h> #include <sys/wait.h> #include <unistd.h> int main() { //管道1,用于子进程发送数据给主进程 int pipefd[2]; //管道数组2,用于主进程分别发数据给子进程 int pipearr[3][5]; pid_t pid; char buff[20]; int i; //建立管道 if(0 > pipe(pipefd)){ printf("Create Pipe Error!\n"); } for(i=0; i<3; i++){ if(0 > pipe(pipearr[i])){ printf("Create Pipe Error!\n"); } } //建立3个子进程 for(i=0; i<3; i++){ pid = fork(); //建立子进程失败 if((pid_t)0 > pid){ printf("Fork Error!\n"); return 3; } //子进程逻辑 if((pid_t)0 == pid){ //发送格式化数据给主进程 FILE *f; f = fdopen(pipefd[1], "w"); fprintf(f, "I am %d\n", getpid()); fclose(f); //接收父进程发过来的数据 read(pipearr[i][0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); //完成后及时退出循环,继续循环会出大问题,和fork的运行逻辑有关! break; } } //主进程逻辑 if((pid_t)0 < pid){ //循环接收全部子进程发过来的数据,而且返回数据给子进程 for(i=0; i<3; i++){ //接收子进程发来的数据 read(pipefd[0], buff, 20); printf("MyPid:%d, Message:%s", getpid(), buff); //发送数据给子进程 write(pipearr[i][6], "Hello My Son\n", 14); } sleep(3); } return 0; }
编译后的运行效果以下:
简单说一下上面的程序逻辑:
首先是有两种管道
第一种只有一个:建立的3个子进程都往这里面写,主进程从这里面读取数据
第二种有一组,每一个子进程一个:主进程分别往3个管道中写,每一个子进程对应从属于本身的管道中读
针对第二种,很明显一个写,一个读,能够保证并发安全。但第一种呢,多个子进程都往一个管道里面写,会不会有问题,这个须要特别注意:
当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
当要写入的数据量大于PIPE_BUF时,Linux将再也不保证写入的原子性
上面多个子进程同时往pipefd中写入的数据小于PIPE_BUF,因此是原子性的,另外只有主进程一个进程在读,因此能够保证数据的完整性。在webbench中其实就是这样使用管道的
能够编译下面的程序,查看PIPE_BUF的值
#include <stdio.h> #include <limits.h> int main() { printf("PIPE_BUF = %d\n", PIPE_BUF); return 0; }
编译后运行效果以下: