想让python实现多进程(multiprocessing),我们要先区分不同的操作系统的不同之处。
Linux操作系统下提供了一个fork()系统调用,普通函数调用一次返回一次,fork()调用一次返回两次,因为操作系统自动把当前进程(父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程则是返回子进程的ID,因为父进程可以fork出很多的子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
python的os模块封装了常见的系统调用,其中就包括fork(),可以在python程序中很容易的创建出子进程:
import os
#注意此代码只适用于like Linux环境下
print('进程{}开始了...').format(os.getpid())
pid = os.fork()
if pid == 0:
print("我是子进程:{},我的父进程是:{}").format(os.getpid(), os.getppid())
else:
print("本进程:{},只创造了一个子进程:{}").format(os.getpid(), pid)
运行结果如下:
进程567开始了...
我是子进程568,我的父进程是567
本进程567,只创造了一个子进程568
由于Windows没有fork调用,上面的代码无法再windows上运行,由于Mac系统是基于BSD内核(Unix的一种),所以可以在Mac上直接运行。
有了fork调用,一个进程在接到一个新任务的时候就可以复制出一个子进程来处理新任务,常见的Apache服务器就是有父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。
multiprocessing
如果你想编写多进程的服务程序,Unix/Linux毫无疑问是较好的选择。同时由于Windows上没有fork调用,难道我们就没有办法在Windows上用python写多进程的程序?当然不是!!
因为python是跨平台的,自然也会提供跨平台的多进程支持,multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了一个Process类来代表一个多进程的对象,下面上实例代码:
from multiprocessing import Process
import os
'''
这是多进程的代码演示
ps:本人的演示代码环境为python2.7(此版本下可加可不加,python很强的可以前后兼容)
python3.x版本下依然可以运行,只需要print的内容加上括号(规范啊规范少年们)
'''
#子进程要执行的代码
def run_proc(name):
print ('run child process {},{}').format(name,os.getpid())
if __name__ == '__main__':
print 'parent process {}'.format(os.getpid())
p = Process(target=run_proc,args=('test',))
print 'child process will start'
p.start()
p.join()
print 'child process end'
运行结果如下:
parent process 8556
child process will start
run child process test,2176
child process end
在创建子进程的时候,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。
join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
Pool(进程池)
如果我们要启动大量的子进程,可以用进程池的方式批量创建子进程:
from multiprocessing import Pool
import os,time,random
'''
这是多进程的代码演示
ps:本人的演示代码环境为python2.7(此版本下可加可不加,python很强的可以前后兼容)
python3.x版本下依然可以运行,只需要print的内容加上括号(规范啊规范少年们)
'''
#子进程要执行的代码
def proc_task(name):
print ('运行子进程 {}号,({})').format(name,os.getpid())
start_time = time.time()
time.sleep(random.random()*3)#随机延时一波
end_time = time.time()
print ('子进程 %s 号 运行了 %0.2f 秒.' % (name, (end_time - start_time)))#此处为百分号占位符,和format作用一样
if __name__ == '__main__':
print ("父进程是{}").format(os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(proc_task,args=(i,))
print ("等待所有子进程执行完毕...")
p.close()
p.join()
print ("所有子进程执行完成")
运行结果为:
父进程是8796
等待所有子进程执行完毕...
运行子进程 0号,(8052)
运行子进程 1号,(12648)
运行子进程 2号,(14200)
运行子进程 3号,(8016)
子进程 2 号 运行了 0.40 秒.
运行子进程 4号,(14200)
子进程 3 号 运行了 1.57 秒.
子进程 1 号 运行了 2.08 秒.
子进程 0 号 运行了 2.90 秒.
子进程 4 号 运行了 2.52 秒.
所有子进程执行完成
光写不讲是坏蛋,下面进入代码解读:
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join之前呢必须先调用close(),调用close()之后就不能继续添加新的Process了。(ps:什么?你问我为什么?门都关上(close)了,还进人(添加新的process),那不得撞得流鼻血(报错)?)
子进程
很多时候,子进程并不是自身,而是一个外部进程。我们创建了子进程后,还要控制子进程的输入和输出。
subprocess模块可以让我们非常方便的启动一个子进程,然后控制其输入和输出。
下面上一个实例(演示了如何在Python代码中运行命令ipconfig,这和命令行直接运行的效果是一样的):
import subprocess
'''
这是多进程的代码演示
ps:本人的演示代码环境为python2.7(此版本下可加可不加,python很强的可以前后兼容)
python3.x版本下依然可以运行,只需要print的内容加上括号(规范啊规范少年们)
'''
print ("$ ipconfig")
r = subprocess.call(['ipconfig'])
print ('exit code',r)
运行结果如下:
Windows IP 配置
无线局域网适配器 WLAN:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
无线局域网适配器 本地连接* 1:
媒体状态 . . . . . . . . . . . . : 媒体已断开连接
连接特定的 DNS 后缀 . . . . . . . :
以太网适配器 以太网:
连接特定的 DNS 后缀 . . . . . . . :
本地链接 IPv6 地址. . . . . . . . : fe80::80b3:647f:c533:86c1%19
IPv4 地址 . . . . . . . . . . . . : 192.168.1.132
子网掩码 . . . . . . . . . . . . : 255.255.255.0
默认网关. . . . . . . . . . . . . : 192.168.1.1
#.........(后面太长就省略掉,知道意思就行,不水)
进程间的通信
Process之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。python的multiprocessing模块包装了底层的机制,提供了Queue,Pipe等多种方式来交换数据。
在这里我们以较为常用的Queue为例子,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读取数据:
from multiprocessing import Process,Queue
import os,time,random
'''
这是多进程的代码演示
ps:本人的演示代码环境为python2.7(此版本下可加可不加,python很强的可以前后兼容)
python3.x版本下依然可以运行,只需要print的内容加上括号(规范啊规范少年们)
'''
#写数据进程
def write_data(q):
print ('我是写入进程:{}').format(os.getpid())
for v in ['a','b','c']:
print ('把{}写入队列...').format(v)
q.put(v)
time.sleep(random.random())
#读数据进程
def read_data(q):
print ('我是读取进程:{}').format(os.getpid())
while True:
value = q.get(True)
print ('从队列里读取到{}...').format(value)
if __name__ == '__main__':
#父进程创建queue,并传递给各个子进程:
q = Queue()
pw = Process(target=write_data,args=(q,))
pr = Process(target=read_data, args=(q,))
#启动子进程pw,写入数据
pw.start()
#启动子进程pr,读取数据
pr.start()
#等待pw结束
pw.join()
#pr,进程里是死循环,无法等待其自动结束,所以手动结束
pr.terminate()
运行结果如下:
我是读取进程:14700
我是写入进程:9772
把a写入队列...
从队列里读取到a...
把b写入队列...
从队列里读取到b...
把c写入队列...
从队列里读取到c...
在Unix/Linux下,multiprocessing模块封装了fork()调用,使我们不需要关注fork()的细节。由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,所有,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了。
小结一波
在like Linux环境下,可以使用fork()调用实现多进程。
要实现跨平台的多进程,可以使用multiprocessing模块。
进程间通信是通过Queue、Pipes等实现的。