前言
面试的时候常常被问道进程间的通信和线程间的通信,静态做个小总结
概括
进程通信主要可以分成以下几种
-
管道
-
消息队列
-
共享内存
-
信号量
-
信号
-
socket
管道
匿名管道
ps auxf | grep mysql
上面命令行里的「|」竖线就是一个管道,它的功能是将前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。
命名管道
- 命名管道是文件
$ mkfifo myPipe
- myPipe 就是这个管道的名称,基于 Linux 一切皆文件的理念,所以管道也是以文件的方式存在,我们可以用 ls 看一下,这个文件的类型是 p,也就是 pipe(管道) 的意思
$ ls -l\
prw-r--r--. 1 root root 0 Jul 17 02:45 myPipe
- 我们往 myPipe 这个管道写入数据:
$ echo "hello" > myPipe // 将数据写进管道\
// 停住了 ...
在开启另外一个线程读取数据
$ cat < myPipe // 读取管道里的数据\
hello
实现原理
- 匿名管道的创建,需要通过下面这个系统调用:
int pipe(int fd[2])
-
这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符
fd[0],另一个是管道的写入端描述符fd[1]。注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。 -
所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外,管道传输的数据是无格式的流且大小受限。
-
你可能会有疑问了,这两个描述符都是在一个进程里面,并没有起到进程间通信的作用,怎么样才能使得管道是跨过两个进程的呢?
-
我们可以使用
fork创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个「fd[0]与fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。
- 在 shell 里面执行
A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。
-
所以说,在 shell 里通过「
|」匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写 shell 脚本时,能使用一个管道搞定的事情,就不要多用一个管道,这样可以减少创建子进程的系统开销。 -
我们可以得知,对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。
-
另外,对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。