一個進程中的多個線程共享一個進程的堆等內存空間,因此實現數據交互是很方便的;但多進程架構中,要想實現多進程間的數據交互相對就困難不少!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; }
編譯後運行效果以下: