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;
}

编译后运行效果以下:

图片描述

相关文章
相关标签/搜索