1. multiprocessing
1.1. Process类
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 14322.
Child process start.
Run child process test(14323)...
child process end.
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process
实例。用start()
方法启动,join()
方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
1.2. Pool类
如果要启动大量的子进程,可以用进程池的方式批量创建子进程。Pool
类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。
栗子:
from multiprocessing import Pool
import os, time, random
def long_time_task(name):
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(random.random() * 3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4)
for i in range(5):
p.apply_async(long_time_task, args=(i,))
print('Waiting for all subprocesses done...')
p.close()
p.join()
print('All subprocesses done.')
执行结果如下:
Parent process 14945.
Waiting for all subprocesses done...
Run task 0 (14946)...
Run task 1 (14947)...
Run task 2 (14948)...
Run task 3 (14949)...
Task 1 runs 0.07 seconds.
Run task 4 (14947)...
Task 4 runs 1.68 seconds.
Task 2 runs 2.07 seconds.
Task 3 runs 2.09 seconds.
Task 0 runs 2.59 seconds.
All subprocesses done
代码解读:
对Pool
对象调用join()
方法会等待所有子进程执行完毕,调用join()
之前必须先调用close()
,调用close()
之后就不能继续添加新的Process
了。请注意输出的结果,task 0,1,2,3
是立刻执行的,而task 4
要等待前面某个task完成后才执行,这是因为Pool的大小设置为了4.
子进程subprocess
很多时候,子进程并不是自身,而是一个外部进程。subprocess
模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。
下面的例子演示了如何在Python代码中运行命令nslookup www.baidu.com
,这和命令行直接运行的效果是一样的:
import subprocess
print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)
运行结果:
$ nslookup www.baidu.com
Server: 162.105.129.122
Address: 162.105.129.122#53
Non-authoritative answer:
www.baidu.com canonical name = www.a.shifen.com.
Name: www.a.shifen.com
Address: 119.75.217.26
Name: www.a.shifen.com
Address: 119.75.217.109
Exit code: 0
如果子进程还需要输入,则可以通过communicate()
方法输入.
进程间通信Queue(允许多个生产者和消费者)
操作系统提供了很多机制来实现进程间的通信。Python的`multiprocessing`模块包装了底层的机制,提供了`Queue`、`Pipes`等多种方式来交换数据。`Pipe()`(用于两个进程之间的连接)、队列`Queue`(允许多个生产者和消费者)。
以Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
print('Process to write: %s' % os.getpid())
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
print('Process to read: %s' % os.getpid())
while True:
value = q.get(True)
print('Get %s from queue.' % value)
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 启动子进程pr,读取:
pr.start()
# 等待pw结束:
pw.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
pr.terminate()
运行结果如下:
Process to write: 15909
Put a to queue...
Process to read: 15910
Get a from queue.
Put b to queue...
Get b from queue.
Put c to queue...
Get c from queue.
multiprocessing使用通常queue.Empty
和 queue.Full
异常来发出超时信号。它们在multiprocessing命名空间中不可用,因此需要从中导入它们 queue
。
Demo2:
from multiprocessing import Queue, Process
import os, random, time
from queue import Empty as QueueEmpty
def getter(name,queue):
print('Son process %s'% name)
while True:
try:
value = queue.get(True,10)
# Queue.get([block[, timeout]])
# block为True,就是如果队列中无数据了。
# |—————— 若timeout默认是None,那么会一直等待下去。
# |—————— 若timeout设置了时间,那么会等待timeout秒后才会抛出Queue.Empty异常
# block 为False,如果队列中无数据,就抛出queue.Empty异常
print("Process %s get : %d"%(name,value))
except QueueEmpty:
break
def putter(name,queue):
print("Son process %s"%name)
for i in range(0,5):
queue.put(i)
# 放入数据 put(obj[, block[, timeout]])
# 若block为True,如队列是满的:
# |—————— 若timeout是默认None,那么就会一直等下去
# |—————— 若timeout设置了等待时间,那么会等待timeout秒后,如果还是满的,那么就抛出Queue.Full.
# 若block是False,如果队列满了,直接抛出queue.Full
print('Process %s put: %d'%(name, i))
time.sleep(random.random())
if __name__ == '__main__':
q = Queue()
getter_process = Process(target = getter,args = ('Getter',q))
putter_process = Process(target = putter,args = ('Putter',q))
getter_process.start()
putter_process.start()
执行结果:
Son process Getter
Son process Putter
Process Putter put: 0
Process Getter get : 0
Process Putter put: 1
Process Getter get : 1
Process Putter put: 2
Process Getter get : 2
Process Putter put: 3
Process Getter get : 3
Process Putter put: 4
Process Getter get : 4
进程间通信Pipe(用于两个进程之间的连接)
Pipe常用来在两个进程间通信,两个进程分别位于管道的两端。
multiprocessing.Pipe([duplex])
(con1, con2) = Pipe()
# con1管道的一端,负责存储,也可以理解为发送信息
# con2管道的另一端,负责读取,也可以理解为接受信息
示例(注意Pipe()构造函数返回的是来两个Pipe实例):
from multiprocessing import Process, Pipe
def send(pipe):
pipe.send(['spam'] + [42, 'egg']) # send 传输一个列表
pipe.close()
if __name__ == '__main__':
(con1, con2) = Pipe() # 创建两个 Pipe 实例
sender = Process(target=send, args=(con1, )) # 函数的参数,args 一定是实例化之后的 Pipe 变量,不能直接写 args=(Pip(),)
sender.start() # Process 类启动进程
print("con2 got: %s" % con2.recv()) # 管道的另一端 con2 从send收到消息
con2.close() # 关闭管道
执行结果:
con2 got: ['spam', 42, 'egg']
管道是可以同时发送和接受消息的(半双工):
from multiprocessing import Process, Pipe
def talk(pipe):
pipe.send(dict(name='Bob', spam=42)) # 传输一个字典
reply = pipe.recv() # 接收传输的数据
print('talker got:', reply)
if __name__ == '__main__':
(parentEnd, childEnd) = Pipe() # 创建两个 Pipe() 实例,也可以改成 conf1, conf2
child = Process(target=talk, args=(childEnd,)) # 创建一个 Process 进程,名称为 child
child.start() # 启动进程
print('parent got:', parentEnd.recv()) # parentEnd 是一个 Pipe() 管道,可以接收 child Process 进程传输的数据
parentEnd.send({x * 2 for x in 'spam'}) # parentEnd 是一个 Pipe() 管道,可以使用 send 方法来传输数据
child.join() # 传输的数据被 talk 函数内的 pip 管道接收,并赋值给 reply
print('parent exit')
执行结果:
parent got: {'name': 'Bob', 'spam': 42}
talker got: {'ss', 'aa', 'pp', 'mm'}
parent exit