果子私房笔记-进程和线程的通信


进程和线程的通信

进程间通讯方式有:管道,信号,信号量,消息队列,共享内存,套接字(socket)共六种

1.管道

本质:管道的本质其实就是内核中的一块缓冲区,多个进程通过访问同一个缓冲区就可以实现进程间的通信。
分类:匿名管道,命名管道

1.1匿名管道

没有具体的文件描述符,只能适用于具有亲缘关系的进程间通信,父进程在创建管道的时候操作系统会返回管道的文件描述符,然后生成子进程时,子进程会通过拷贝父进程的pcb来获取到这个管道的描述符。
读写特性:1. 没数据 read 阻塞。2. 满数据,write 阻塞。3. 所有读端pipefd[0]关闭,调用 write 导致异常退出进程。4. 所有写端pipefd[1]关闭,调用 read 读完数据,返回0。

1.2命名管道

命名管道也是内核中的一块缓冲区,但是它具有标识符,这个标识符是一个可见于文件系统的管道文件,能够被其他进程找到并打开管道文件来获取管道的操作句柄。
open打开命名管道的特性:1. 若文件以只读打开,则会阻塞,直到文件被以写的方式打开。2. 若文件以只写打开,则会阻塞,直到文件被以读的方式打开。

管道的特性:

(1).半双工通信(数据只能在一个方向上流动)
(2).管道自带同步和互斥
(3).读写特性,无数据则读阻塞,满数据写阻塞,所有读关闭则写端异常,所有写端关闭则读端读完数据后不堵塞返回0。
(4).管道声明周期随进程
(5).管道提供字节流传输服务
(6).命名管道额外有一个打开特性

2.共享内存

定义:开辟物理内存上的空间,然后在进程的页表上,将同一个物理内存映射到自己的虚拟地址空间,各进程通过自己的虚拟空间来共享访问这一块空间。
特点:
(1).共享内存是最快的进程间通信方式,因为进程是直接对内存进行存取。
(2).因为多个进程可以同时操作,所以需要使用信号量进行同步。
接口:
创建 int shmget(key_t key, size_t size, int shmflg)
映射 void *shmat(int shmid, const void *shmaddr, int shmflg)
共享内存管理 int shmctl(int shmid, int cmd, struct shmid_ds *buf)
解除映射关系 int shmdt(const void *shmaddr)
问题:
(1).超过共享内存的大小限制怎么办?修改参数
(2).同一个进程多次进行shmat会出现什么问题?
指向这块共享内存的引用计数会增加,比较消耗虚拟内存空间8

3.消息队列:

定义:是消息的链表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
特点:
(1).消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
(2).消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
(3).消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

4.信号

定义:信号是在软件层次上对中断机制的一种模拟,在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的,用于通知接收进程某个事件已经发生。如果接收进程并未处于执行状态,则该信号就由内核保存起来,知道该进程恢复执行并传递给他为止。如果一个信号可以被进程设置为阻塞,如果如此,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。
响应:
(1).忽略信号,即对信号不做任何处理 (有 2 个例外:sigkill,sigstop)
(2).捕捉信号,定义信号处理函数,当信号发生时,执行相应的处理函数
(3).默认操作,Linux对每种信号都规定了默认操作

5.信号量

定义:它是一个临界变量。用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点:
(1).信号量用于进程间同步,若要在进程间传递数据通常结合共享内存。
(2).信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
(3).每次对信号量的 PV 操作不仅限于对信号量值加1或减1,而且可以加减任意正整数。
(4).支持信号量组。

6.套接字( socket )

介绍:套解字也是一种进程间通信机制(传输层实现端到端的通信),与其他通信机制不同的是,它可用于不同机器间的进程通信。它可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。因此,两个应用程序之间的数据传输要通过套接字来完成。
socket()函数:socket是一种”打开—读/写—关闭”模式的实现,服务器和客户端各自维护一个”文件”,在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
bind()函数:bind()函数把一个地址族中的特定地址赋给socket。(该函数只有服务器端才需要调用)
listen()函数:如果作为一个服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。(该函数只有服务器端才需要调用)
accept()函数:当客户端通过调用connect()函数发送链接请求后。一旦服务器监听到该请求之后,就会调用accept()函数接收请求,这样连接就建立好了,之后就可以开始网络I/O操作了,类同于普通文件的读写I/O操作。(该函数只有服务器端才需要调用)
connect()函数:客户端如果想发送链接请求,首先需要调用socket()函数,来创建socket描述符,然后就可以调用connect()函数来向服务器端发送请求。 (该函数只有客户端端才需要调用)
read()、write()等函数:既然一个套接字端点被表示为一个文件描述符,因此只要建立链接,就可以使用为本地文件设计的read和write函数。
close()函数: 在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。

注:

缺点

1.无名管道:速度慢,容量有限,只有父子进程能通讯。(需要四次拷贝才能交流数据)
2.有名管道:任何进程间都能通讯,但速度慢。
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快(只需要两次拷贝),但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存

线程

1.是通过共享变量,线程之间通过该变量进行协作通信;
2.通过队列(本质上也是线程间共享同一块内存)来实现消费者和生产者的模式来进行通信;

线程同步:保证线程之间协调工作,能互相配合。比如后面的线程需要用到前面线程产生的数据。(一定程度上和线程互斥有相似)(举例生产者消费者问题)
线程异步:异步可以按规定顺序去执行线程,多用于 比如一个网页信息 ,(你把他分成2个线程或者多个,先读取它的title,时间,作者等等 第二个或者其他进程用于读取数据量较大的正文内容,比如图片)。


文章作者: xucanxx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 xucanxx !
  目录