【多线程与高并发】进程间通信的五种方式
进程间通信
为什么要进程间通信?
- 为了解决进程和进程之间共享数据的问题。
管道
-
管道可以分为两类:匿名管道和命名管道。
匿名管道:它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「
|
」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。命名管道:突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。
-
无名管道通信(pipe)
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。
-
有名管道通信(named pipe)
有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信,任意两进程之间的通信。
-
高级管道通信(popen)
将另一个程序当做一个新的进程在当前程序进程中启动,则它算是当前程序的子进程,这种方式我们成为高级管道方式。
无名管道和有名管道的联系和区别
1、联系
- 通信数据只存在于内存缓冲页面中;
- 都是半双工通信;
2、区别
- 无名管道是无名的,有名管道是有名的;
- 无名管道只能用于父子进程或兄弟进程之间的通信,而有名管道可用于任意两进程之间通信;
- 无名管道是无形的,即无名管道的 inode 结构不是在磁盘上存储的,而是临时生成的,而有名管道的 inode 结点在磁盘上。
-
-
将一个程序的输出交给另一个程序进行处理
-
存在同步和堵塞问题
常见的Linux命令 “|” 其实就是匿名管道,表示把一个进程的输出传输到另外一个进程,如:
echo "Happyjava" | awk -F 'j' '{print $2}' # 输出 ava
另外,我们可以通过 mkfifo 命令创建一个命名管道,如:
mkfifo pipe
一个进程往管道输入数据,则会阻塞等待别的进程从管道读取数据:
消息队列
- 消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。
- 缺点:或收到数据块最大长度的限制约束,如果频繁的发生进程间的通信行为,那么进程需要频繁地读取队列中的数据到内存中,相当于间接地从一个进程拷贝到另一个进程,这需要花费时间
- 消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。
- 消息队列有:kafka、rabbitMQ、RocketMQ
共享内存
-
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问
-
是最快的进程间通信方式,解决了拷所消耗的时间
-
共享内存最大的问题就是多进程竞争内存,即线程安全问题,多进程竞争同个共享资源会造成数据的错乱。
信号量
-
信号量是一个计数器,可以用来控制多个进程对共享资源的访问
-
信号量就是解决线程安全问题的一种方式
例如信号量的初始值是 1,然后 a 进程来访问内存1的时候,我们就把信号量的值设为 0,然后进程b 也要来访问内存1的时候,看到信号量的值为 0 就知道已经有进程在访问内存1了,这个时候进程 b 就会访问不了内存1。所以说,信号量也是进程之间的一种通信方式。
- 常作为锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源
Socket
- 前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。
- 创建 Socket 的类型不同,分为三种常见的通信方式:
- 基于 TCP 协议的通信方式
- 基于UDP协议的通信方式
- 本地进程间通信方式