进程池用法
from multiprocessing import Pool
def func(n):
print('做了第%s个蛋糕' % n)
if __name__ == '__main__':
p = Pool(4)
for i in range(500):
p.apply_async(func, args=(i,)) # 异步提交func到一个子进程中执行
p.close() # 关闭进程池,用户不能再向这个池中提交任务了
p.join() # 阻塞直到进程池中所有的任务都被执行完
大纲
用multiprocess中的Pool起进程池
进程池中开启的个数:默认是cpu个数
提交任务(不能传队列作为子进程的参数,只能传管道)
- apply 同步提交,直接返回结果
- apply_async 异步提交,返回对象,通过对象获取返回值
异步提交时要用p.close() 和p.join()
- map
可以拿到返回值的可迭代对象,循环就可以获取返回值了。
简化了apply_async的操作
使用进程池在多个进程之间通信和返回值的问题:
使用第三方工具:数据安全,redis,memcache,kafka。
进程池
测试:不能传队列作为子进程的参数
from multiprocessing import Pool,Queue,Pipe
def func(q):
print(q)
if __name__ == '__main__':
p = Pool()
q = Queue()
p.apply(func, args=(q,))
p.close()
p.join()
运行结果
RuntimeError: Queue objects should only be shared between processes through inheritance # 不让放队列
测试:能传管道作为子进程的参数
from multiprocessing import Pool,Queue,Pipe
def func(q):
print(q)
if __name__ == '__main__':
p = Pool()
q = Pipe()
p.apply(func, args=(q,))
p.close()
p.join()
运行结果
(<multiprocessing.connection.PipeConnection object at 0x0000018E9F041160>, <multiprocessing.connection.PipeConnection object at 0x0000018E9F0A5C50>) # 返回管道两端内存地址
- 为什么要有进程池
开启过多的进程并不能提高你的效率,反而会降低你的效率,假设有500个任务,同时开启500个进程,这500个进程除了不能一起执行之外(cpu没有那么多核),操作系统调度这500个进程,让他们平均在4个或8个cpu上执行,这会占用很大的空间。 - 程序分为两种类型:
- 计算型:
适合开启多进程,但是不适合开启很多多进程。
因为计算型能充分占用cpu。适合使用多进程,因为多进程能充分利用多核,如操作系统全部有20个进程,起4个进程,有4个cpu,假设都没有阻塞,那一个进程可以占全部时间片的1/5。
但是如果500个任务起500个进程,光起进程这件事都要花很长时间,而且进程的调度也会变得非常大,所以起进程的个数是要合理安排的, - IO型:
不适合开启多进程。
因为开起多进程的目的是为了充分利用多核。IO型大部分时间都在等,所以一个cpu轮流执行就够了。毕竟多进程是要占用操作系统资源去调度的。
大部分时间都在阻塞队列里,而不是在运行状态中,如所有socket程序,爬虫,文件操作等都是IO密集型,大部分都是在等待,如等待网络输出,等待文件打开,等待端口开,等待有人连接等,这时即使开了多进程,充分占用了cpu,也不会提高多大效率。
现在的问题是假设有500个任务,又不能多开进程,可以开5个进程,让这5个进程分别做这500个任务。
-
信号量,多进程,进程池的区别
信号量:如果有500件衣服要做,于是招了500个人,只有4台机器,谁完成任务了再进几个人,也就是4个信号量,500个进程,机器是cpu,4个cpu,每个人是每个进程,每件衣服是一个任务
多进程:500个人抢四台机器,每个人只能工作十分钟,相对于时间片,不管做没有做完,就走了,就是500个进程做500个任务
进程池:4个人做500件衣服,4台机器,谁来谁就可以做衣服。而对于多进程来说,操作系统来调度500个人谁用机器,谁不用机器,和调度4个人是不一样的。所以不能有几件衣服就雇几个人,而是都几台机器就雇几个人。这样人都转起来了,并且都能完成任务。而且工厂负担也小一点,这就是池的概念。 -
例1 apply_async() 异步提交任务
异步提交任务特点:全异步,主进程和子进程都异步了,当主进程代码结束后,程序就结束了,进程池里面的子进程全部被回收了,
所以要用到join方法:阻塞直到进程池中的所有任务都执行完毕。
在没有返回值的情况下,要想所有任务能够顺利执行完毕,就要用p.close(), p.join(), res.get(), get() 方法不能在提交任务之后立刻执行。应该是先提交所有任务再通过get()取返回值。- 测试:主进程结束后子进程是否结束
def task(num):
open('file%s' % num, 'w').close()
if __name__ == '__main__':
p = Pool()
for i in range(20):
p.apply_async(task, args=(i,))
time.sleep(0.1)
运行结果
主进程睡0.1秒就结束了,只新建了7个文件,子进程的任务是新建20个任务。说明进程池随着主进程的代码结束而结束了。并没有等进程池里面的任务全执行完才退出整个程序。
所以如果想要所有任务都执行完再结束,就要用到p.join()方法
- 测试:异步提交能不能拿到子进程的返回值
def task(num):
print(num)
return num ** 2
if __name__ == '__main__':
p = Pool()
for i in range(20):
res = p.apply_async(task, args=(i,))
print(res)
p.close()
p.join()
运行结果
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFBA8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFC88>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFD30>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFDD8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFEB8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFAFF98>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA0B8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA198>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA278>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA358>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA438>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA518>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA5F8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA6D8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA7B8>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA898>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBA978>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBAA58>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBAB38>
<multiprocessing.pool.ApplyResult object at 0x000001DCBDFBAC18>
# 这些地址是函数返回值的地址。用get()方法可取出这些内存地址中的值。
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
用get方法拿结果
def task(num):
time.sleep(1)
print('%s : %s' % (num, os.getpid()))
return num ** 2
if __name__ == '__main__':
p = Pool()
for i in range(5):
res = p.apply_async(task, args=(i,))
print(res.get())
运行结果
0 : 2336
0
1 : 7336
1
2 : 4400
4
3 : 11444
9
4 : 11660
16
结果是每个子进程同步了,因为拿到了地址后还要对应的值,且一定要打印出来,由于程序执行完了才能return,如果直接等程序执行完了后才拿结果,就变成同步的程序了,所以把所有回执先放到列表里,
- 通过异步的方式取返回值
def task(num):
time.sleep(1)
print('%s : %s' % (num, os.getpid()))
return num ** 2
if __name__ == '__main__':
p = Pool()
res_lst = []
for i in range(5):
res = p.apply_async(task, args=(i,)) # 先把任务全提交
res_lst.append(res)
for res in res_lst:
print(res.get()) # get本身就有阻塞的效果了,就不需要join再阻塞一次了,谁先拿到结果先取谁就行了
运行结果
0 : 15404
0
1 : 9724
1
2 : 21472
4
3 : 15084
9
4 : 1300
16
例
import time
from multiprocessing import Pool
def func(num):
print('做了第%s件衣服' % num)
if __name__ == '__main__':
start = time.time()
p = Pool(4) # 创建4个进程,拿到一个小的进程池。
for i in range(500):
p.apply_async(func, args=(i,)) # 把func叫给一个进程执行,异步提交func到一个子进程中执行。# 不用Process起进程了,而是用进程池起进程,
p.close() # 不是关闭所有任务,而是关闭进程池。用户不能向池里面提交任务了,
p.join() # 阻塞。由于主进程和进程池里面的任务完全异步了,我要进程池里面的任务全执行完主进程才结束。
print(time.time() - start) # 查看执行时间
运行结果
做了第0件衣服
.
.
.
做了第499件衣服
0.3361020088195801
对比开100个进程时间
import time
from multiprocessing import Pool,Process
def func(num):
print('做了第%s件衣服' % num)
if __name__ == '__main__':
start = time.time()
p_list = []
for i in range(100):
p = Process(target=func,args=(i,))
p.start()
p_list.append(p)
for i in p_list:
i.join()
print(time.time() - start)
运行结果
做了第0件衣服
.
.
.
做了第499件衣服
15.145474433898926 # 开500个进程用了15秒,而大部分时间都用在开启进程上面了。真正执行任务的时间特别短,不仅开启进程花时间,关闭一个进程也要花时间的,开启和关闭要执行500次。而开4个进程,同样是500 个任务用了0.33秒。只开启和销毁4个进程,中间可以用这四个进程做很多事情,所以任务多的情况下特别是高计算型的。用进程池。
- 例2:apply() 同步提交任务
apply 这个方法提供了返回值:
子进程对应函数的返回值
同步提交任务弊端:
一个一个顺序执行的,并没有任何并发效果
import os
import time
from multiprocessing import Pool
def task(num):
time.sleep(1)
print('%s : %s' % (num, os.getpid()))
return num ** 2
if __name__ == '__main__':
p = Pool(4) # 参数就是进程数。默认为None,进程数为os.cpu_count() cpu的个数,并交给进程池。
for i in range(20):
res = p.apply(task, args=(i,)) # 进程池中同步提交任务的方法,没有并发效果,提交完1个后自带join方法,等着上一个任务结束。
print('-->',res) # 获取返回值的机制:res为主进程在子进程中拿的一个返回值,通常主进程和子进程直接数据是不共享的,进程池的机制实现了拿子进程的返回值,虽然不能直接实现return语句,但是可以把return的值放到队列,然后从队列里面拿,Pool就是这种IPC机制实现的。只不过进程池可以直接拿。
运行结果
0 : 16204
--> 0
1 : 8016
--> 1
2 : 10696
--> 4
3 : 13552
--> 9
4 : 16204
--> 16
5 : 8016
--> 25
6 : 10696
--> 36
7 : 13552
--> 49
8 : 16204
--> 64
9 : 8016
--> 81
10 : 10696
--> 100
11 : 13552
--> 121
12 : 16204
--> 144
13 : 8016
--> 169
14 : 10696
--> 196
15 : 13552
--> 225
16 : 16204
--> 256
17 : 8016
--> 289
18 : 10696
--> 324
19 : 13552
--> 361
- 进程池中map() 方法
def task(num):
time.sleep(1)
print('%s : %s' % (num, os.getpid()))
return num ** 2
if __name__ == '__main__':
p = Pool()
p.map(task, range(20)) # 异步提交的简化版本,能够取代close和 join方法,因为map方法自带join和close方法,# 进程池中的map于内置函数map的区别是同步和异步,
运行结果
0 : 21532
1 : 20724
2 : 12376
3 : 21876
4 : 3096
5 : 22500
7 : 78086 : 19520
8 : 20804
9 : 14648
10 : 6016
11 : 7080
12 : 21532
13 : 20724
14 : 12376
15 : 21876
16 : 3096
17 : 22500
18 : 1952019 : 7808
- 测试用map()提交任务时取返回值
from multiprocessing import Pool,Queue,Pipe
import time
def func(q):
print(q)
time.sleep(1)
return q
if __name__ == '__main__':
p = Pool()
ret = map(func, range(20))
for i in ret:
print('-->', i)
运行结果(异步)
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
--> 0
--> 1
--> 2
--> 3
--> 4
--> 5
--> 6
--> 7
--> 8
--> 9
--> 10
--> 11
--> 12
--> 13
--> 14
--> 15
--> 16
--> 17
--> 18
--> 19