进程池

进程池用法

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上执行,这会占用很大的空间。
  • 程序分为两种类型:
  1. 计算型:
    适合开启多进程,但是不适合开启很多多进程。
    因为计算型能充分占用cpu。适合使用多进程,因为多进程能充分利用多核,如操作系统全部有20个进程,起4个进程,有4个cpu,假设都没有阻塞,那一个进程可以占全部时间片的1/5。
    但是如果500个任务起500个进程,光起进程这件事都要花很长时间,而且进程的调度也会变得非常大,所以起进程的个数是要合理安排的,
  2. 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
  • 6
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值