Linux開發簡單多進程應用

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

編譯後運行效果以下:

圖片描述

相關文章
相關標籤/搜索