概述
通常来说,多线程是一个好东西。不过由于Python的GIL的限制,多线程更适合于I/O密集型应用(I/O释放了GIL,可以允许更多的并发),而不是计算密集型应用。对于后一种情况而言,为了实现更好的并行性,你需要使用多进程,以便让CPU的其他内核来执行。
multiprocessing
如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。由于Windows没有fork调用,难道在Windows上无法用Python编写多进程的程序?由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束:
from multiprocessing import Process
import os
# 子进程要执行的代码
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start.')
p.start()
p.join()
print('Child process end.')
执行结果如下:
Parent process 928.
Process will start.
Run child process test (929)...
Process end.
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
进程间通信:
multiprocessing支持两种进程间通信的方式:Queues(队列)和Pipes(管道)
Queues示例:
from multiprocessing import Process, Queue
def f(q):
q.put([42, None, 'hello'])
if __name__ == '__main__':
q = Queue()
p = Process(target=f, args=(q,))
p.start()
print(q.get()) # prints "[42, None, 'hello']"
p.join()
Queues是线程和进程安全的。
Pipes示例:
from multiprocessing import Process, Pipe
def f(conn):
conn.send([42, None, 'hello'])
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=f, args=(child_conn,))
p.start()
print(parent_conn.recv()) # prints "[42, None, 'hello']"
p.join()
Pipe()返回的两个连接对象代表着管道的两个端,每个连接对象都有send()和recv()方法。
常用的方法:
start() # 启动进程。
join() # 等待子进程结束。
is_alive() # 用于检查子进程是否已经结束。
terminate() # 强制结束进程。
subprocess
很多时候,子进程并不是自身(自己定义的方法/函数),而是一个外部进程(exe可执行程序)。subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
subprocess模块创建子进程有两个方法:run()和Popen(),Popen相比run有更多的参数可以设置,功能更强,但一般当run方法满足不了需求时我们才选Popen,因为run比Popen使用起来更简单一些。
举个检查网络是否已连接的例子,其实就是调用cmd命令行执行ping命令:
import subprocess
result = subprocess.run('ping www.baidu.com', stdout=subprocess.PIPE)
print(result.stdout.decode('gb2312'))
运行结果:
正在 Ping www.a.shifen.com [14.215.177.39] 具有 32 字节的数据:
来自 14.215.177.39 的回复: 字节=32 时间=3ms TTL=51
来自 14.215.177.39 的回复: 字节=32 时间=3ms TTL=51
来自 14.215.177.39 的回复: 字节=32 时间=3ms TTL=51
来自 14.215.177.39 的回复: 字节=32 时间=3ms TTL=51
14.215.177.39 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 3ms,最长 = 3ms,平均 = 3ms
常用的方法:
Popen.poll() # 用于检查子进程是否已经结束。设置并返回returncode属性。
Popen.kill() # 杀死子进程。
Popen.wait() # 等待子进程结束。设置并返回returncode属性。
Popen.pid # 获取子进程的进程ID。
进程优缺点
优点:
多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)。
缺点:
多进程模式的缺点是创建进程、进程间切换开销大。
总结
调用外部程序,使用subprocess;让进程执行自己编写的模块,则使用multiprocessing。