原文:https://morvanzhou.github.io/learning-steps/
多进程 Multiprocessing
目录
1、什么是Multiprocessing
多进程 Multiprocessing 和多线程 threading 类似, 他们都是在 python 中用来并行运算的. 不过既然有了 threading, 为什么 Python 还要出一个 multiprocessing 呢? 原因很简单, 就是用来弥补 threading 的一些劣势, 比如在 threading 教程中提到的GIL.
使用 multiprocessing 也非常简单, 如果对 threading 有一定了解的朋友, 你们的享受时间就到了. 因为 python 把 multiprocessing 和 threading 的使用方法做的几乎差不多. 这样我们就更容易上手. 也更容易发挥你电脑多核系统的威力了!
2、添加进程 Process
import multiprocessing as mp
import threading as td
"""定义一个被线程、进程调用的函数"""
def job(a,d):
print('aaaaa')
"""创建线程、进程;分别启动、连接(join)"""
t1 = td.Thread(target=job,args=(1,2))
p1 = mp.Process(target=job,args=(1,2))
t1.start()
p1.start()
t1.join()
p1.join()
aaaaa
从上面的使用对比代码可以看出,线程和进程的使用方法相似
3、存储进程输出 Queue
Queue的功能是将每个核或进程的运算结果放在队里中, 等到每个进程或核运行完毕后再从队列中取出结果, 继续加载运算。原因很简单, 多进程调用的函数不能有返回值, 所以使用Queue存储多个进程运算的结果
import multiprocessing as mp
def job(q):
res=0
for i in range(1000):
res+=i+i**2+i**3
q.put(res) #queue
if __name__=='__main__':
q = mp.Queue()
p1 = mp.Process(target=job,args=(q,))
p2 = mp.Process(target=job,args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
res1 = q.get()
res2 = q.get()
print(res1+res2)
一直在运行!!!
循环次数改成10,也不行
4、效率对比 threading & multiprocessing
上篇讲了多进程/多核的运算,这次我们来对比下多进程,多线程和什么都不做时的消耗时间,看看哪种方式更有效率。
和上节一样,首先import multiprocessing并定义要实现的job(),同时为了容易比较,我们将计算的次数增加到1000000
import multiprocessing as mp
import threading as td
import time
def job(q):
res = 0
for i in range(1000000):
res += i+i**2+i**3
q.put(res) # queue
def multicore():
q = mp.Queue()
p1 = mp.Process(target=job, args=(q,))
p2 = mp.Process(target=job, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
res1 = q.get()
res2 = q.get()
print('multicore:' , res1+res2)
def normal():
res = 0
for _ in range(2):
for i in range(1000000):
res += i+i**2+i**3
print('normal:', res)
def multithread():
q = mp.Queue()
t1 = td.Thread(target=job, args=(q,))
t2 = td.Thread(target=job, args=(q,))
t1.start()
t2.start()
t1.join()
t2.join()
res1 = q.get()
res2 = q.get()
print('multithread:', res1+res2)
if __name__ == '__main__':
st = time.time()
normal()
st1= time.time()
print('normal time:', st1 - st)
multithread()
st2 = time.time()
print('multithread time:', st2 - st1)
multicore()
print('multicore time:', time.time()-st2)
# range(1000000)
('normal:', 499999666667166666000000L)
('normal time:', 1.1306169033050537)
('thread:', 499999666667166666000000L)
('multithread time:', 1.3054230213165283)
('multicore:', 499999666667166666000000L)
('multicore time:', 0.646507978439331)
普通/多线程/多进程的运行时间分别是1.13,1.3和0.64秒。 我们发现多核/多进程最快,说明在同时间运行了多个任务。 而多线程的运行时间居然比什么都不做的程序还要慢一点,说明多线程还是有一定的短板的
# range(10000000)
('normal:', 499999666667166666000000L)
('normal time:', 1.1306169033050537)
('thread:', 499999666667166666000000L)
('multithread time:', 1.3054230213165283)
('multicore:', 499999666667166666000000L)
('multicore time:', 0.646507978439331)
这次运行时间依然是 多进程 < 普通 < 多线程,由此我们可以清晰地看出哪种方法更有效率。
5、进程池 Pool
进程池就是我们将所要运行的东西,放到池子里,Python会自行解决多进程的问题
5.1 进程池 Pool() 和 map()
然后我们定义一个Pool
pool = mp.Pool()
有了池子之后,就可以让池子对应某一个函数,我们向池子里丢数据,池子就会返回函数返回的值。 Pool和之前的Process的不同点是丢向Pool的函数有返回值,而Process的没有返回值。
接下来用map()获取结果,在map()中需要放入函数和需要迭代运算的值,然后它会自动分配给CPU核,返回结果
res = pool.map(job, range(10))
import multiprocessing as mp
def job(x):
return x*x
def multicore():
pool = mp.Pool()
res = pool.map(job, range(10))
print(res)
if __name__ == '__main__':
multicore()
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
总结
- Pool默认调用是CPU的核数,传入processes参数可自定义CPU核数
- map() 放入迭代参数,返回多个结果
- apply_async()只能放入一组参数,并返回一个结果,如果想得到map()的效果需要通过迭代
6、共享内存 shared memory
这节我们学习如何定义共享内存。只有用共享内存才能让CPU之间有交流。
Shared Value
我们可以通过使用Value数据存储在一个共享的内存表中。
import multiprocessing as mp
value1 = mp.Value('i', 0)
value2 = mp.Value('d', 3.
其中d和i参数用来设置数据类型的,d表示一个双精浮点类型,i表示一个带符号的整型。
Shared Array
在Python的mutiprocessing中,有还有一个Array类,可以和共享内存交互,来实现在进程之间共享数据。
array = mp.Array('i', [1, 2, 3, 4])
这里的Array和numpy中的不同,它只能是一维的,不能是多维的。同样和Value 一样,需要定义数据形式,否则会报错。