Skip to content

Latest commit

 

History

History
81 lines (63 loc) · 5.22 KB

理解进程间通信.md

File metadata and controls

81 lines (63 loc) · 5.22 KB

##理解进程间通讯 IPC是允许多进程间通信的一种机制。
有许多IPC的实现方式,他们依赖于不同架构的运行环境。运行在同一台机器上的进程可以由多种进程间通信方式,比如共享内存、消息队列和管道。如果多进程处于物理上分布式集群上上,我们可以使用RPC的方式。 第五章中我们介绍了Multiprocessing和ProcessPoolExecutor模块,学习了常规的管道的用法。我们学习了同一个父进程的各个子进程间通信方式,但是有时候我们需要在不相关的进程间传递信息(子进程不共有同一个父进程),我们会想,通过不相关进程的地址空间,是否能够在它们之间建立通信。然而一个进程不能获取另外一个进程的地址空间,因此我们需要使用一种新的机制——命名管道。

###探索命名管道 对于POSIX系统,例如linux,我们可以把所有东西归结为文件,我们每操作一个任务,我们可以找到一个文件与之对应,同时还有一个文件描述符与该文件相联系,通过文件描述符就可以操作该文件.

命名管道是一种允许通过某些特殊文件的文件描述符实现IPC通讯的机制.这些特殊文件使用特殊的模式(例如先入先出)来读写数据.在对信息的管理上命名管道不同于常规管道,命名管道通过文件系统中的特殊文件及其文件描述符来实现,而普通管道是在内存中创建的.

###在python中使用命名管道 在python中使用命名管道很容易. 下面我们会通过两个非直接通讯的程序来展示如何使用命名管道. 第一个程序名为write_to_named_pip.py,它的作用是写一条22个字节的消息到管道中,该消息包括一个信息字符串及产生消息的PID. 第二个程序名为read_from_named_pip.py, 它会从管道中读取消息并展示消息内容及其PID.

在执行的最后,read_from_named_pipe.py进程会显示一条形如"I pid [] received a message => Hello from pid [the PID of writer process"的消息

为了展示读写进程的相互依赖关系,我们会在两个独立的控制台上运行这两个程序. 在展示结果前,让我们先分析一下两个程序的代码.

####往命名管道写入数据 在python中,命名管道是通过系统调用来实现的. 下面我们会逐行解释write_to_named_pipe.py中代码的功能.

我们首先导入os模块,这样我们在后面的代码中才能调用系统调用.

import os

接下来我们会解释__main__代码块,在该代码块中创建了命名管道以及一个用于存储消息的FIFO的特殊文件. __main__代码块中的第一行代码定义了命名管道的标签.

named_pipe = "my_pipe"

接下来我们检查该命名管道是否已经存在,若不存在,则调用mkfifo系统调用来创建这个命名管道.

if not os.path.exists(named_pipe):
    os.mkfifo(named_pipe)

mkfifo调用会创建一个特殊的文件,该文件对通过命名管道读写的消息实现了FIFO机制.

我们再以一个命名管道和一个行如"Hello from pid [%d]"的消息来作为参数调用函数write_message. 该函数会将消息写入到(作为参数传递给它的)命名管道所代表的文件中. write_message函数定义如下:

def write_message(input_pipe, message):
    fd = os.open(input_pipe, os.O_WRONLY)
    os.write(fd, (message % str(os.getpid())))
    os.close(fd)

我们可以观察到,在函数的第一行,我们调用一个系统调用:open. 该系统调用若成功的话会返回一个文件描述符, 通过该文件描述符我们就能够读写那个FIFO文件中的数据. 请注意,我们可以通过flags参数控制打开FIFO文件的模式. 由于write_message函数紧紧需要写数据,因此我们使用如下代码:

fd = os.open(input_pipe, os.O_WRONLY)

在成功打开命名管道后,我们使用下面代码写入消息:

os.write(fd, (message % os.getpid()))

最后,请一定记着使用close关闭通讯渠道,这样才能释放被占用的计算机资源.

os.close(fd)

####从命名管道读取数据 我们实现了read_from_pipe.py来读取命名管道. 当然,改程序也需要借组os模块才能操作命名模块. 改程序的主要代码很简单:首先,我们定义了所使用命名管道的标签,该标签需要与写进程所用的命名管道同名.

named_pipe = "my_pipe"

然后,我们调用read_message函数,该函数会读取write_to_named_pipe.py写入的内容. read_message函数的定义如下:

def read_message(input_type):
    fd = os.open(input_pipe, os_RONLY)
    message = (
        "I pid [%d] received a message => %s"
            % (os.getpid(), os.read(fd, 22))
    os.close(fd)
    return message

上面代码中,open调用相比无需再介绍了,唯一没见过的只有read调用. 该调用从命名管道中读取指定字节的内容. 这里我们从文件描述符中读取22个字节的内容. 消息被读取出来之后,又被该函数作为返回值返回. 最后依然记得调用close调用来关闭通讯渠道.

最终,下面的截屏显示了write_to_named_pip和read_from_named_pipe程序的执行结果.