Python多进程(Multiprocessing)
仅供个人学习
来源于 莫凡Python:https://mofanpy.com/tutorials/python-basic/basic/ 侵删
什么是 Multiprocessing 和 threading 的比较
多进程 Multiprocessing 和多线程 threading 类似, 他们都是在 python 中用来并行运算的. 不过既然有了 threading, 为什么 Python 还要出一个 multiprocessing 呢? 原因很简单, 就是用来弥补 threading 的一些劣势, 比如在 threading 中的 GIL。
使用 multiprocessing 也非常简单, 如果对 threading 有一定了解的朋友, 你们的享受时间就到了. 因为 python 把 multiprocessing 和 threading 的使用方法做的几乎差不多. 这样我们就更容易上手. 也更容易发挥你电脑多核系统的威力了!
添加进程 Process
import multiprocessing as mp
import threading as td
def job(a,d):
print('aaaaa')
# Thread和Process的首字母都要大写,被调用的函数没有括号,被调用的函数的参数放在args(...)中
t1 = td.Thread(target=job,args=(1,2))
p1 = mp.Process(target=job,args=(1,2))
t1.start()
p1.start()
t1.join()
p1.join()
# 线程和进程使用方法相似
存储进程输出 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)
效率对比 threading & multiprocessing
普通/多线程/多进程的运行时间分别是
1.13
,1.3
和0.64
秒。 我们发现多核/多进程最快,说明在同时间运行了多个任务。 而多线程的运行时间居然比什么都不做的程序还要慢一点,说明多线程还是有一定的短板的。GIL多进程 < 普通 < 多线程
进程池 Pool
进程池
Pool
。 进程池就是我们将所要运行的东西,放到池子里,Python会自行解决多进程的问题
进程池 Pool() 和 map()
有了池子之后,就可以让池子对应某一个函数,我们向池子里丢数据,池子就会返回函数返回的值。
Pool
和之前的Process
的不同点是丢向Pool
的函数有返回值,而Process
的没有返回值。接下来用
map()
获取结果,在map()
中需要放入函数和需要迭代运算的值,然后它会自动分配给CPU核,返回结果
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()
自定义核数量
我们怎么知道
Pool
是否真的调用了多个核呢?我们可以把迭代次数增大些,然后打开CPU负载看下CPU运行情况打开CPU负载(Mac):活动监视器 > CPU > CPU负载(单击一下即可)
Pool
默认大小是CPU的核数,我们也可以通过在Pool
中传入processes
参数即可自定义需要的核数量,
def multicore():
pool = mp.Pool(processes=3) # 定义CPU核数量为3
res = pool.map(job, range(10))
print(res)
apply_async()
Pool
除了map()
外,还有可以返回结果的方式,那就是apply_async()
.
apply_async()
中只能传递一个值,它只会放入一个核进行运算,但是传入值时要注意是可迭代的,所以在传入值后需要加逗号, 同时需要用get()
方法获取返回值
def multicore():
pool = mp.Pool()
res = pool.map(job, range(10))
print(res)
res = pool.apply_async(job, (2,))
# 用get获得结果
print(res.get())
# 结果
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # map()
4 # apply_async()
用 apply_async() 输出多个结果
apply_async()
只能输入一组参数。在此我们将
apply_async()
放入迭代器中,定义一个新的multi_res
def multicore():
pool = mp.Pool()
res = pool.map(job, range(10))
print(res)
res = pool.apply_async(job, (2,))
# 用get获得结果
print(res.get())
# 迭代器,i=0时apply一次,i=1时apply一次等等
multi_res = [pool.apply_async(job, (i,)) for i in range(10)]
# 从迭代器中取出
print([res.get() for res in multi_res])
总结
Pool
默认调用是CPU的核数,传入processes
参数可自定义CPU核数map()
放入迭代参数,返回多个结果apply_async()
只能放入一组参数,并返回一个结果,如果想得到map()
的效果需要通过迭代
共享内存 shared memory
Shared Value
我们可以通过使用
Value
数据存储在一个共享的内存表中。
import multiprocessing as mp
# 其中d和i参数用来设置数据类型的,d表示一个双精浮点类型,i表示一个带符号的整型。
value1 = mp.Value('i', 0)
value2 = mp.Value('d', 3.14)
Shared Array
在Python的
mutiprocessing
中,有还有一个Array
类,可以和共享内存交互,来实现在进程之间共享数据。这里的
Array
和numpy中的不同,它只能是一维的,不能是多维的。同样和Value
一样,需要定义数据形式,否则会报错。
array = mp.Array('i', [1, 2, 3, 4])
# 错误形式
array = mp.Array('i', [[1, 2], [3, 4]]) # 2维list
"""
TypeError: an integer is required
"""
参考数据类型
| Type code | C Type | Python Type | Minimum size in bytes |
| --------- | ------------------ | ----------------- | --------------------- |
| `'b'` | signed char | int | 1 |
| `'B'` | unsigned char | int | 1 |
| `'u'` | Py_UNICODE | Unicode character | 2 |
| `'h'` | signed short | int | 2 |
| `'H'` | unsigned short | int | 2 |
| `'i'` | signed int | int | 2 |
| `'I'` | unsigned int | int | 2 |
| `'l'` | signed long | int | 4 |
| `'L'` | unsigned long | int | 4 |
| `'q'` | signed long long | int | 8 |
| `'Q'` | unsigned long long | int | 8 |
| `'f'` | float | float | 4 |
| `'d'` | double | float | 8 |
(来源:https://docs.python.org/3/library/array.html)
进程锁 Lock
不加进程锁
import multiprocessing as mp
import time
def job(v, num):
for _ in range(5):
time.sleep(0.1) # 暂停0.1秒,让输出效果更明显
v.value += num # v.value获取共享变量值
print(v.value, end="")
def multicore():
v = mp.Value('i', 0) # 定义共享变量
p1 = mp.Process(target=job, args=(v,1))
p2 = mp.Process(target=job, args=(v,3)) # 设定不同的number看如何抢夺内存
p1.start()
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
multicore()
在上面的代码中,我们定义了一个共享变量
v
,两个进程都可以对它进行操作。 在job()
中我们想让v
每隔0.1秒输出一次累加num
的结果,但是在两个进程p1
和p2
中设定了不同的累加值。进程1和进程2在相互抢着使用共享内存
v
。
加进程锁
为了解决上述不同进程抢共享资源的问题,我们可以用加进程锁来解决。
首先需要定义一个进程锁
l = mp.Lock() # 定义一个进程锁
然后将进程锁的信息传入各个进程中
p1 = mp.Process(target=job, args=(v,1,l)) # 需要将Lock传入
p2 = mp.Process(target=job, args=(v,3,l))
在
job()
中设置进程锁的使用,保证运行时一个进程的对锁内内容的独占
def job(v, num, l):
l.acquire() # 锁住
for _ in range(5):
time.sleep(0.1)
v.value += num # v.value获取共享内存
print(v.value)
l.release() # 释放
完整代码:
def job(v, num, l):
l.acquire() # 锁住
for _ in range(5):
time.sleep(0.1)
v.value += num # 获取共享内存
print(v.value)
l.release() # 释放
def multicore():
l = mp.Lock() # 定义一个进程锁
v = mp.Value('i', 0) # 定义共享内存
p1 = mp.Process(target=job, args=(v,1,l)) # 需要将lock传入
p2 = mp.Process(target=job, args=(v,3,l))
p1.start()
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
multicore()
运行一下,让我们看看是否还会出现抢占资源的情况:
1
2
3
4
5
8
11
14
17
20
显然,进程锁保证了进程p1
的完整运行,然后才进行了进程p2
的运行