进程是孤立的,但可以批次通信,成为进程间通信(Interprocess Communication,IPC)。进程间通信最常见形式是基于消息传递,一条消息就是一块原始字节的缓存,然后就可以使用像send()和recv()这样的操作,通过I/O通道(如管道或网络套接字)来传递或接受信息。另一种不太常见的IPC机制依赖于内存映射区域,借助内存映射,进程可以创建共享的内存区域,如果对这些内存区域进行修改,那么查看这些区域的所有进程都能看到这些修改。
在大多数系统上,python同时支持消息传递和基于线程的并发编程。尽管大多数程序员熟悉的往往是线程接口,实际上python线程收到限制很多,python解释器使用了内部GIL(Global Interpreter Lock,全局解释器锁定),在任意指定的时刻只允许单个python线程执行,无论有多少个可用的CPU。
如果要在python中进行各种类型的并行编程,消息传递很可能是必须熟练掌握的概念,即便使用线程,常用的方法也是将应用程序的结构设计为大量独立的线程集合,这些线程通过消息队列交换数据,这种方法极大地减少了对使用锁和其他同步手段的需求,消息传递还会自然扩展到网络和分布式系统中。
multiprocessing模块
multiprocessing模块为在子进程中运行任务,通信和共享数据,以及执行各种形式的同步提供支持。这个编程接口有意模仿threading模块中线程的编程接口,但和线程不同,进程没有任何共享状态。
下面重点描述multiprocessing
Process([group [, target [, name [, args [, [kwargs]]]]])
target为进程启动时执行的可调用对象,args是传递给target位置参数的元组,而kwargs是传递给target的关键字参数的字典。如果省略args和kwargs参数,将不带参数调用target,name是为进程指定描述性名称的字符串,group参数未使用,始终为None。
Process实例具有以下方法:
p.is_alive() 如果p仍在运行,返回True
p.join([timeout]) 等待进程p终止,timeout是可选的超时期限,进程可以被连接无数次,但如果连接自身则会出错。
p.run() 进程启动时运行的方法,默认情况下,会调用传递给Process构造函数target,定义进程的了另一种方法是从Process类继承并重新实现run()函数。
p.start() 启动进程,这将运行代表进程的子进程,并调用该子进程的p.run()函数。
p.terminate() 强制终止进程,如果调用此函数,进程p将被立刻终止,同时不会进行任何清理动作,如果进程p创建了它自己的子进程,这些进程将变为僵死进程,使用此方法需要特别小心,如果p保存一个锁定或者通过进程间通信被调用,那么终止它可能会导致死锁或I/O崩溃。
Process实例具有以下数据属性
p.authkey 进程的身份验证键,除非显式设定这是由os.urandom()函数生成32字符的字符串,这个键的用途是为涉及网络连接的底层进程间通信提供安全性。这类连接只有在两端具有相同的身份验证键时才能成功。
p.daemon一个boolean标志,指示进程是否是后台进程。当创建它的python进程终止时,后台(Daemonic)进程将自动终止,另外,禁止后台进程创建自己的新进程。p.daemon的值必须在使用p.start()函数启动之前进行设置。
p.exitcode进程的整数退出代码,如果进程仍然在运行,它的值为None,如果值为负数,-N表示进程由信号N所终止。
p.name进程的名字
p.pid进程的整数进程ID
进程间通信
multiprocessing模块支持进程间通信的两种主要形式:管道和队列。这两种方法都是使用消息传递实现的,但队列接口有意模仿线程程序中常见的队列用法。
Queue([maxsize])
创建共享的进程队列。maxsize是队列中允许的最大项数,如果省略此参数,则无大小限制,底层队列使用管道好锁定实现。另外,还需要运行支持线程以便将队列中的数据传输到底层管道中。
Queue实例具有以下方法:
q.cancel_join_thread() 不会在进程退出自动连接后台线程。这可以防止join_thread()方法阻塞
q.close() 关闭队列,防止队列中加入更多数据,调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果q被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常,例如,如果某个使用者正在被阻塞在get()操作上,关闭生产者中的队列不会导致get()方法返回错误
q.empty() 如果调用此方法时q为空,返回True,如果其他进程或线程正在往队列中添加项目,结果是不可靠的,也就是说,在返回和使用结果之间,队列可能已经加入了新的项目。
q.full() 如果q已满,返回True,由于线程的存在,结果也可能不可靠
q.get([block [, timeout]]) 返回q中的一个项目,如果q为空,此方法将阻塞,知道队列中有项目可用为止。block用于控制阻塞行为,默认为True,如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout是可选超时间,用于阻塞模式中。如果在指定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。
q.get_nowait() 同q.get(False)
q.join_thread() 连接队列的后台线程,此方法用于调用q.close()方法之后,等待所有队列项被消耗。默认情况下,此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。
q.put(item [,block [, timeout]]) 将item放入队列,如果队列已满,此方法将阻塞至有空间可有为止。block控制阻塞行为,默认为True,如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。
q.qsize() 返回队列中目前项目的正确数量。此函数的结果并可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。某些系统上,此方法可能引发NotImplementedError异常。
JoinableQueue([maxsize]) 创建可连接的共享进程队列,这就是一个Queue对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现。
q.task_done() 使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。
q.join() 生产者使用此方法进行阻塞,直到队列中的所有项目均被处理。阻塞将持续到为队列中的每一个项目均调用q.task_done()方法为止