在 Linux/Unix 系统下,可以使用 os.fork() 创建、管理子进程,但是这种方法在 Windows 下是行不通的,而且我并没在实际开发中用过这种方式,所以这里只介绍 Multiprocessing 模块的使用方法。我会直接讲解用法,至于进程、线程的概念这里默认读者是了解的。
multiprocessing 提供 Process 类来代表一个进程,通过实例化创建一个新子进程
from multiprocessing import Process, Queue
def power(x, res):
for _ in range(4):
x *= x
res.put(x)
if __name__ == '__main__':
res = Queue()
p = Process(target=power, args=(2, res))
p.start()
p.join()
result = res.get()
print(result)
代码说明:
那么,如果要批量创建进程呢?后面会介绍使用进程池的方法,但是这里我还是给出一个用 Process 批量创建进程的示例,注意如何批量 join 子进程:
from multiprocessing import Process, Queue
def power(x, res):
for _ in range(4):
x *= x
res.put(x)
if __name__ == '__main__':
res = Queue()
nums = [1, 2, 3]
processes = []
for num in nums:
p = Process(target=power, args=(num, res))
p.start()
processes.append(p)
for name in processes:
name.join()
for i in range(len(nums)):
print(res.get())
当我们需要批量创建进程时,如果用 Process,如上面的示例,事情就会变得很麻烦,通常是在循环内部重复创建进程,而且要循环 join 子进程。这时候使用进程池实现同样的功能就会简单优雅的多:
from multiprocessing import Pool
def power(x):
for _ in range(4):
x *= x
return x
if __name__ == '__main__':
res = []
nums = [1, 2, 3]
p = Pool(processes=3)
for num in nums:
temp = p.apply_async(power, args=(num,))
res.append(temp.get())
p.close()
p.join()
for result in res:
print(result)
代码说明:
还有没有更好的方法呢? 当然有了!就是使用 map 方法,同样的功能示例如下:
from multiprocessing import Pool
def power(x):
for _ in range(4):
x *= x
return x
if __name__ == '__main__':
nums = [1, 2, 3]
p = Pool(processes=3)
res = p.map(power, nums)
p.close()
p.join()
for result in res:
print(result)
代码说明:
那么,假如目标函数有不止一个变量呢? 虽然使用 map 方法既高效又简洁,但是也有一个弊端,就是单纯使用 map 函数,目标函数只可以有一个变量,因为 map 方法的可迭代对象中的值对应的都是相同的变量。下面提供两种思路:
思路一:
from multiprocessing import Pool
def power(para):
x, time = para[0], para[1]
for _ in range(time):
x *= x
return x
if __name__ == '__main__':
para = [(1,4), (2,4), (3,4)]
p = Pool(processes=3)
res = p.map(power, para)
p.close()
p.join()
for result in res:
print(result)
代码说明:
思路二:
from multiprocessing import Pool
from functools import partial
def power(x, time):
for _ in range(time):
x *= x
return x
if __name__ == '__main__':
nums = [1, 2, 3]
p = Pool(processes=3)
res = p.map(partial(power, time=4), nums)
p.close()
p.join()
for result in res:
print(result)
代码说明:
上述的两种思路各有所长,使用哪种方式也是视场合而定。
这里仅介绍使用 multiprocessing 中的 Queue 和 Pipe 进行进程间通信。(因为其他的方式我不大懂。。。。)
在一开始介绍 Process 类时就用到了 Queue 获取目标函数返回值,这其实就是一种进程间通信。Queue 是多进程安全的队列,在实例化 Queue 时接受一个参数 maxsize 来限制其中的元素个数。可以通过 put 方法把元素从队尾插入,通过 get 方法把元素从队首取出。这两个方法都有两个参数:blocked 和 timeout:
下面的代码演示了使用 Queue 进行进程间通信:
from multiprocessing import Process, Queue
def put_it(x, q):
for _ in range(4):
x *= x
q.put(x)
def get_it(q):
for _ in range(4):
value = q.get()
q.put(value-value)
if __name__ == '__main__':
q = Queue()
p1 = Process(target=put_it, args=(2, q))
p2 = Process(target=get_it, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
for _ in range(4):
print(q.get())
这段代码打印出的结果就是四个 0
Pipe 的作用就像它的名字一样,充当进程间数据的管道,在实例化 Pipe 时接受一个参数 duplex,来决定其是双向的还是单向的(默认为 True,即双向管道,但是实际上是由两个单向管道构成的二元组)。
下面的代码演示了使用 Pipe 进行进程间通信:
from multiprocessing import Process, Queue, Pipe
def put_1(p, q):
x = 'hello'
p[0].send(x)
temp = 'put_1: ' + p[0].recv()
q.put(temp)
def put_2(p, q):
y = 'nice to meet you'
p[1].send(y)
temp = 'put_2: ' + p[1].recv()
q.put(temp)
if __name__ == '__main__':
p = Pipe()
q = Queue()
p1 = Process(target=put_1, args=(p, q))
p2 = Process(target=put_2, args=(p, q))
p1.start()
p2.start()
p1.join()
p2.join()
for _ in range(2):
print(q.get())
这段代码打印出来为:
put_1: nice to meet you
put_2: hello
有一些情况还可以考虑使用其他方法进行加速,毕竟创建进程的开销是非常大的。比如:计算密集型的(memory bound)程序可以使用 Cython 获得加速;network bound 的程序可以使用多线程加速,这种情况下是没有 GIL 的,创建线程的开销要比进程小的多。在实际开发中更多时候,是在程序不同的位置分别采用合适的加速手段来实现性能的最优化。