基本知识:
一、什么是多进程 Multiprocessing
支持并行运算,充分利用计算机的多核CPU
二、添加多进程 Process
import multiprocessing as mp
def process_job(a,b):
print("a:",a)
print("b:",b)
if __name__=="__main__":
p1 = mp.Process(target=process_job,args=(1,2)) # 定义一个进程p1
p1.start() # 开启进程p1
p1.join() # 将进程p1加入到主进程
三、存储进程输出 Queue
多进程调用的函数不能有返回值,故使用Queue存储结果。
import multiprocessing as mp
def process_job(q):
res = 0
for i in range(10):
res += i
q.put(res)
if __name__=="__main__":
q = mp.Queue()
p1 = mp.Process(target=process_job,args=(q,)) # 注意args必须是可迭代的类型,但只有一个参数的时候,要加一个逗号
p2 = mp.Process(target=process_job,args=(q,))
p1.start()
p2.start()
p2.join()
p1.join()
print("res1:",q.get())
print("res2:",q.get())
四、效率对比 threading & multiprocessing
import multiprocessing as mp
import threading as td
import time
def job(q):
res = 0
for i in range(100000):
res += i + i**2 + i**3
q.put(res)
def multiProcess(): # 多进程运算
q = mp.Queue()
p1 = mp.Process(target=job,args=(q,))
p2 = mp.Process(target=job,args=(q,))
p1.start()
p2.start()
p2.join()
p1.join()
print("multiProcess_res:",q.get()+q.get())
def multiThread(): # 多线程运算
q = mp.Queue()
t1 = td.Thread(target=job,args=(q,))
t2 = td.Thread(target=job,args=(q,))
t1.start()
t2.start()
t2.join()
t1.join()
print("numliThread_res:",q.get()+q.get())
def normal(): # 普通运算
res = 0
for _ in range(2):
for i in range(100000):
res += i + i**2 + i**3
print("normal_res:",res)
if __name__=="__main__":
t1 = time.time()
normal()
t2 = time.time()
print("normal_time:",t2-t1)
multiProcess()
t3 = time.time()
print("multiProcess_time:",t3-t2)
multiThread()
t4 = time.time()
print("multiThread_time:",t4-t3)
结果:运行时间 多进程 < 多线程 (多线程和普通的运行时间差不多)
normal_res: 49999666671666600000
normal_time: 0.2157750129699707
multiProcess_res: 49999666671666600000
multiProcess_time: 0.14542102813720703
numliThread_res: 49999666671666600000
multiThread_time: 0.2052018642425537
五、进程池 Pool
相当于批量处理多进程
与 Process 不同的是,Pool 有返回值,返回值是一个 list,依次是每个进程执行的结果。
import multiprocessing as mp
def job(x):
return x*x
def multicore():
pool = mp.Pool(processes=2)
res = pool.map(job, range(10)) # 方式一:使用 pool 的 map 函数
print(res)
res = pool.apply_async(job, (2,))
print(res.get())
multi_res =[pool.apply_async(job, (i,)) for i in range(10)] #方式二:使用 pool 的 apply_async 函数
print([res.get() for res in multi_res])
if __name__ == '__main__':
multicore()
六、共享内存 shared memory
在上一篇文章的第六部分线程锁可以看到,对于一个全局变量,多个线程之间共享全局变量
,一个线程对全局变量修改以后,其他线程访问到的是修改后的全局变量,会造成数据的混乱。
与多线程不同的是,多进程不会共享全局变量
,多个进程之间不进行交流,任意一个进程对全局变量的修改都不会影响到其他进程对全局变量的访问。
比如:
import multiprocessing as mp
def job1():
global A
for i in range(3):
A += 1
print("Job1:",A)
def job2():
global A
for i in range(3):
A += 10
print("Job2:",A)
if __name__=="__main__":
A = 0
p1 = mp.Process(target=job1)
p2 = mp.Process(target=job2)
p1.start()
p2.start()
p2.join()
p1.join()
结果:互不影响
1
2
10
3
20
30
因此,需要设置共享内存,让进程之间进行交流:
1、Shared Value
通过使用 Value 将数据存储在一个共享的内存表中
import multiprocessing as mp
value1 = mp.Value('i', 0)
value2 = mp.Value('d', 3.14)
其中 d 和 i 参数用来设置数据类型的,d 表示一个双精浮点类型,I 表示一个带符号的整型。
2、Shared Array
mutiprocessing中,有还有一个Array类,可以和共享内存交互,来实现在进程之间共享数据
array = mp.Array('i', [1, 2, 3, 4])
这里的Array和numpy中的不同,它只能是一维的,不能是多维的。同样和Value 一样,需要定义数据形式,否则会报错。
参数数据类型表
七、进程锁 Lock
1、不加进程锁
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()
结果:进程1和进程2在相互抢着使用共享内存v
1
4
5
8
9
12
13
16
17
20
2、加进程锁
import multiprocessing as mp
import time
def job(v, num, l):
l.acquire()
for _ in range(10):
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))
p2 = mp.Process(target=job, args=(v, 3, l))
p1.start()
p2.start()
p1.join()
p2.join()
if __name__ == '__main__':
multicore()
结果:显然,进程锁保证了进程p1的完整运行,然后才进行了进程p2的运行
1
2
3
4
5
8
11
14
17
20