多进程编程通信(1)

多进程编程通信(1)

多进程编程通信(1)_第1张图片

文章目录

  • 多进程编程通信(1)
    • IPC
    • 管道
      • 匿名管道
      • 具名管道
        • 2个进程例子
      • 总结
    • Reference
      • >>>>> 欢迎关注公众号【三戒纪元】 <<<<<

IPC

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

每个进程有各自不同的进程地址空间,互相独立,一般不能互相访问,一个进程的变量在另一个进程中看不到,但是内核空间是进程间共享的,进程间通信需要通过内核,内核中开辟空间,不同进程在该缓冲区内写入数据、读出数据,实现通信。

多进程编程通信(1)_第2张图片

一般的进程通信分为:管道、消息队列、共享内存、信号量、信号和Socket。

多进程编程通信(1)_第3张图片

管道

管道(英语:Pipeline)是一系列将标准输入输出链接起来的进程,其中每一个进程的输出被直接作为下一个进程的输入。

每一个链接都由匿名管道实现[来源请求]。管道中的组成元素也被称作过滤程序。

一般使用过linux系统的同学都使用过管道符同时执行多个程序,例如:

ls -l | less

ls 和 less 分别是两个独立的进程。shell 会将 ls 的输出结果作为 less 的输入结果,然后再由 less 把处理结果投放到终端上。

上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令(ls -l)的输出,作为后一个命令(less)的输入。

多进程编程通信(1)_第4张图片

可以看出管道传输数据是单向的,和实际的管道很像,如果想相互通信,我们需要创建两个管道才行。

匿名管道

使用C语言在UNIX中使用pipe系统调用时,这个函数会让系统构建一个匿名管道,这样在进程中就打开了两个新的,打开的文件描述符:一个只读端和一个只写端。

管道的两端是两个普通的,匿名的文件描述符,这就让其他进程无法连接该管道。

为了避免死锁并利用进程的并行运行的好处,有一个或多个管道的UNIX进程通常会调用fork产生新进程。并且每个子进程在开始读或写管道之前都会关掉不会用到的管道端。

或者进程会产生一个子线程并使用管道来让线程进行数据交换。

#include 
#include 
#include 
#include 
#include 

int
main(int argc, char *argv[])
{
  int pipefd[2];
  pid_t cpid;
  char buf;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s \n", argv[0]);
    exit(EXIT_FAILURE);
  }

  if (pipe(pipefd) == -1) {
    perror("pipe");
    exit(EXIT_FAILURE);
  }

  cpid = fork();
  if (cpid == -1) {
    perror("fork");
    exit(EXIT_FAILURE);
  }

  if (cpid == 0) {    /* Child reads from pipe */
    close(pipefd[1]);          /* Close unused write end */
    printf("\nchild receives data from father: \n");
    while (read(pipefd[0], &buf, 1) > 0)
      write(STDOUT_FILENO, &buf, 1);
      printf("%c", buf);
    write(STDOUT_FILENO, "\n", 1);
    close(pipefd[0]);
    _exit(EXIT_SUCCESS);

  } else {            /* Parent writes argv[1] to pipe */
    close(pipefd[0]);          /* Close unused read end */
    write(pipefd[1], argv[1], strlen(argv[1]));
    printf("\nfather wirte data: %s\n", argv[1]);
    close(pipefd[1]);          /* Reader will see EOF */
    wait(NULL);                /* Wait for child */
    exit(EXIT_SUCCESS);
  }
}

形象化思路就是:

多进程编程通信(1)_第5张图片

所以父进程写入管道的时候,子线程就读出数据,不能两个进程同时写入,会造成数据冲突。因为是单向的,所以要么父进程写入,子进程读出,要么子进程写入,父进程读出。

运行程序:

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test/multithreading$ ./randy Randy

father wirte data: Randy

child receives data from father: 
Randy

在 shell 里面执行 A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系。他们是并列的2个进程,共用1个管道

具名管道

具名管道可以通过调用mkfifo或mknod来构建,当被调用时表现为输入或输出的文件。

创建具名管道接口:

int mkfifo(const char *pathname, mode_t mode);

int mknod (const char *__path, __mode_t __mode, __dev_t __dev)
    
// 返回值:成功返回0;失败返回-1

命名管道的特性

  1. 若文件以只读的方式打开,则会阻塞,直到文件被以写的方式打开
  2. 若文件以只写的方式打开,则会阻塞,直到文件被以读的方式打开

这样可以允许建立多个管道,并且将其同标准错误重定向或tee结合起来使用更为有效。 实现代码:

#include 
#include 
#include 
#include  
#include  
#include 
#include 

int main(int argc, char *argv[]) {
  char filename[] = "randy_fifo";
  if (!mkfifo(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) {
    pid_t pid = fork();
    if (pid == 0) {  // child
      int fd = open(filename, O_WRONLY);
      if (fd < 0)
        perror("child open()");
      else {
        printf("\nchild write data : %s\n", argv[1]);
        if (strlen(argv[1]) != write(fd, argv[1], strlen(argv[1])))
          perror("child write error");
        else
          close(fd);
      }
    } else if (pid > 0) {  // father
      int fd = open(filename, O_RDONLY);
      if (fd < 0)
        perror("father open()");
      else {
        char buffer[200];
        int readed = read(fd, buffer, 199);
        printf("\father read data from child: %s\n", buffer);
        close(fd);
        buffer[readed] = '\0';
        printf("%s\n", buffer);
      }
    } else {
      perror("fork()");
    }
  } else {
    perror("mkfifo() error:");
  }

  return 0;
}

执行结果:

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test/multithreading$ ./randy RandyJeff

child write data : RandyJeff

ather read data from child: RandyJeff
RandyJeff

linux系统一切皆是文件的思路,发现具名管道也是文件,匿名管道可以看成是内存中的文件:

(base) qiancj@qiancj-HP-ZBook-G8:~/codes/test/multithreading/pipe$ ll randy.fifo 
prw-rw-r-- 1 qiancj qiancj 0 818 10:12 randy.fifo|

2个进程例子

fifo_read.c

//fifo_read.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    umask(0);
    //创建管道文件
    int ret = mkfifo("./randy.fifo",0664 );
    //管道文件不是因为存在而创建失败
    if (ret < 0 && errno != EEXIST)
    {
        perror("mkfifo error");
        return -1;
    }
    //以只读方式获取管道文件的操作句柄
    int fd = open("./randy.fifo", O_RDONLY);
    if (fd < 0)
    {
        perror("open fifo error");
        return -1;
    }
    printf("open fifo success\n");
    int i = 0;
    //一直读数据
    while(1)
    {
        char buf[1024] = {0};
        //将从管道读取的文件写到buf中
        int ret = read(fd, buf, 1023);
        ++i;
        if (ret < 0)
        {
            perror("read error");
            return -1;
        }
        //所有写端都关闭
        else if (ret == 0)
        {
            perror("all write closed");
            return -1;
        }
        //打印所读到的数据
        printf("read buf:[%s] %d\n",buf, i);
    }
    close(fd);
    return 0;
}

fifo_write.c

//fifo_write.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
    umask(0);
    int ret = mkfifo("./randy.fifo", 0664);
    if (ret < 0 && errno != EEXIST)
    {
        perror("mkfifo error");
        return -1;
    }
    //以只写的方式打开管道文件
    int fd = open("./randy.fifo", O_WRONLY);
    if (fd < 0)
    {
        perror("open fifo error");
        return -1;
    }
    printf("open fifo success\n");
    int i = 0;
    //一直写
    while(1)
    {
        char buf[1024] = {0};
        //将字符串内容写到buf中,并记录写的次数
        sprintf(buf, "Hello Randy", i++);
        //将buf中的内容写到管道中
        write(fd, buf, strlen(buf));
        printf("write data success\n");
        sleep(1);
    }
    close(fd);
    return 0;
}

编译2个程序后,先启动读的进程,程序在阻塞,等待写入:

执行写进程,读进程就能够读到数据了:

多进程编程通信(1)_第6张图片

停掉写进程,只读进程中的read接口会返回0,通过该值控制进程退出,读进程也会停止

多进程编程通信(1)_第7张图片

总结

对于匿名管道,通信范围是父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。

另外,对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

在 Linux 系统中,我们可以使用下图的方式来查看各级 CPU Cache 的大小,比如我这手上这台服务器,离 CPU 核心最近的 L1 Cache 是 32KB,其次是 L2 Cache 是 256KB,最大的 L3 Cache 则是 3MB。

(base) qiancj@qiancj-HP-ZBook-G8:~$ cat /sys/devices/system/cpu/cpu0/cache/index0/size 
48K
(base) qiancj@qiancj-HP-ZBook-G8:~$ cat /sys/devices/system/cpu/cpu0/cache/index1/size 
32K
(base) qiancj@qiancj-HP-ZBook-G8:~$ cat /sys/devices/system/cpu/cpu0/cache/index2/size 
1280K
(base) qiancj@qiancj-HP-ZBook-G8:~$ cat /sys/devices/system/cpu/cpu0/cache/index3/size 
24576K

Reference

  • https://mp.weixin.qq.com/s?__biz=MzUxMjEyNDgyNw==&mid=2247514907&idx=1&sn=0989d590cd479eee5b9d86cb7356ce1a&chksm=f96bc9efce1c40f943823ee8354573134b84a3cc842e96fb4412fe985f13ece3ec5c7c816fb7&mpshare=1&scene=1&srcid=0816z8wo8qDR630oSFqCITSZ&sharer_sharetime=1692179392154&sharer_shareid=9282c33bd664bcf868293e52a214b059#rd

  • https://cloud.tencent.com/developer/article/1736932

  • https://huaweicloud.csdn.net/63564439d3efff3090b5cdec.html#FIFO_108

  • https://zh.wikipedia.org/wiki/%E7%AE%A1%E9%81%93_(Unix)

  • https://xiaolincoding.com/os/4_process/process_commu.html#%E7%AE%A1%E9%81%93


>>>>> 欢迎关注公众号【三戒纪元】 <<<<<

你可能感兴趣的:(C++,linux,服务器)