这天,笔者正在思考着Java中线程的通信方式,室友突然问道:“进程的通信方式有哪些呀?”
笔者暗自思索:“原来不光线程,进程之间也是存在许多通信方式的!”
赶紧来学习一下好啦!
共享存储
- 基于共享数据结构的通信方式:比如共享空间里只能放一个长度为10的数组。仅适用于传递相对少量的数据,通信效率低,属于低级通信。
- 基于共享存储区的通信方式:在内存中画出一张共享存储区,数据的形式、存放位置都由进程控制,而不是操作系统。相比之下,这种共享方式速度更快,是一种高级通信方式。
注意:两个进程对共享空间的访问必须是互斥的。
为了阻止多个进程同时读写共享的数据,我们需要的是互斥,即以某种手段确保当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。我们把对共享内存进行访问的程序片段称作临界区域或临界区。
下面讨论几种实现互斥的方案,在这些方案中,当一个进程在临界区中更新共享内存时,其他进程将不会进入其临界区,也不会带来任何麻烦。
实现互斥的基本方法
-
软件实现方法
在进入区设置并检查一些标志来标明是否有进程在临界区中,若已有进程在临界区,则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。常见的例子为Peterson’s Algorithm。
-
Peterson’s Algorithm
为了防止两个进程为进入临界区而无限期等待,除了标志值数组 flag[ ] 之外,又设置了变量turn,每个进程在先设置自己的标志后再设置turn标志。这时,再同时检测另一个进程状态标志和不允许进入标志,以便保证两个进程同时要求进入临界区时,只允许一个进程进入临界区。
进入区的代码如下:
//Pi进程 flag[i] = true; turn = j; while(flag[j] && turn == j); //Pj进程 flag[j] = true; turn = i; while(flag[i] && turn == i);
逻辑如下:
- 主动争取
- 主动谦让
- 检查对方是否也想使用,且最后一次自己是不是说了"客气话"
如何共享turn变量:多数现代操作系统(包括UNIX和Windows)提供了一种方法,让进程与其他进程共享其部分地址空间。在这种方法中,缓冲区和其他数据结构可以共享。在最坏的情况下,如果没有可共享的途径,则可以使用共享文件。
-
-
硬件实现方法
-
中断屏蔽方法
在单处理器系统中,最简单的方法是使每个进程在刚刚进入临界区后立即屏蔽所有中断,并在就要离开之前再打开中断。屏蔽中断后,时钟中断也被屏蔽。CPU只有发生时钟中断或其他中断时才会进行进程切换,这样,在屏蔽中断之后CPU将不会被切换到其他进程。这样,在屏蔽中断之后CPU将不会被切换到其他进程。于是,一旦某个进程屏蔽中断之后,它就可以检查和修改共享内存,而不必担心其他进程介入。
注意:可以解决单个CPU核上的临界区问题,如果在多个核心中,关闭中断不能阻塞其他进程执行。
-
硬件指令方法
TestAndSet指令(TSL指令):这条指令是原子操作,即执行该代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。可以为每个临界资源设置一个共享布尔变量lock,表示资源的两种状态:true表示正被占用,初值为false。在进程访问临界资源之前,利用TestAndSet检查和修改标志lock:若有进程在临界区,则重复检查,直到进程退出。
原理:执行TSL指令的CPU将锁住内存总线,以禁止其他CPU在本指令结束之前访问内存。
-
-
信号量
信号量是Dijkstra提出的一种方法。在他的建议中引入了一个新的变量类型,称作信号量(semaphore)。可以用一个信号量表示系统中某种资源的数量。
为确保信号量能正常工作,最重要的是要采用一种不可分割的方式来实现它。在单核CPU下,只需在必要的时候屏蔽全部中断即可;在多核CPU下,则每个信号量应由一个锁变量进行保护。通过TSL之类的指令来确保同一时刻只有一个CPU在对信号量进行操作。
-
互斥量
如果不需要信号量的计数能力,有时可以使用一个简化版本,称为互斥量。互斥量是一个可以处于两态之一的变量:解锁和加锁。这样,只需要一个二进制位表示它。
管程
为了更易于编写的程序,科学家提出了一种高级同步原语,称为管程(monitor)。一个管程是一个由过程、变量及数据结构等组成的一个集合,它们组成一个特殊的模块或软件包。任一时刻管程中只能有一个活跃进程。
C语言并不支持管程,有一种支持管程的语言是Java。只要将关键字synchronized加入到方法声明中,Java保证一旦某个线程执行该方法,就不允许其他线程执行该对象的任何synchronized方法。没有关键字synchronized,就不能保证没有交错执行。
消息传递
由于信号量太低级了,而管程在少数几种编程语言之外又无法使用,并且,这些原语均为提供机器间的信息交换方法。因此我们需要消息传递。这种进程间通信的方法使用两条原语send和receive,它们像信号量而不像管程,是系统调用而不是语言成分。
- 直接通信方式
发送进程利用OS所提供的发送原语直接把消息发给目标进程 - 间接通信方式
发送和接收进程都通过共享实体(邮箱)的方式进行消息的发送和接收。当使用信箱时,在send和receive调用中的地址参数就是信箱的地址,而不是进程的地址。
管道通信
管道通信是Unix等宏内核系统中非常重要的进程间通信机制,是消息传递的一种特殊方式。管道是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件(pipe文件)。
管道机制需要提供一下几点的协调能力
- 互斥,即当一个进程正在对pipe执行读/写操作时,其它进程必须等待
- 同步,当一个进程将一定数量的数据写入,然后就去睡眠等待,直到读进程将数据取走,再去唤醒。读进程与之类似
- 确定对方是否存在
管道可以理解为共享存储的优化和发展。写进程会先把缓冲区写满,然后才让读进程读,当缓冲区中还有数据时,写进程不会往缓冲区写数据。当然,这也决定了管道通信必然是半双工通信。
以上就是笔者对进程间的通信方式的总结了,这一部分很多博客和书都有讲到,但很多地方仍然让人似懂非懂,所以笔者又根据看过的书总结了一遍,并加入了一些自己的理解。如有纰漏,欢迎指正。