python进阶(五、并发编程:池、协程)

2.并发编程
2.6 池:concurrent.futures模块
2.6.1 简介
池:预先的开启固定个数的进程数/线程数,当任务来临的时候,直接提交给已经开好的进程/线程,让这个进程/线程去执行就可以了。
池节省了进程、线程的开启、关闭、切换需要的时间,并且减轻了操作系统调度的负担。
当有新的请求提交到进程池中时,如果池未满,则会分配一个进程用来执行该请求;反之,如果池中的进程数已经达到规定最大值,那么该请求就会等待,只要池中有进程空闲下来,该请求就能得到执行。
池缺点:一个池中的任务个数限制了我们程序的并发个数
concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
concurrent.futures模块出现过程:
(1)threading模块没有提供池
(2)multiprocessing模块仿照threading编写,并增加了Pool功能
(3)concurrent.futures模块出现,包含了线程池和进程池,两者使用方法相似
通常进程池中进程的数量= CPU核数+1(一般大于CPU核心数,小于CPU核数2)
进程池适用于高计算场景(没有I/O操作:没有文件操作,没有数据库操作、没有网络操作、没有input),这种场景很少。
通常线程池中线程的数量= CPU核数
5(一般根据I/O比例定制,推荐CPU核数*5)
线程池和进程池的用法基本相同,下面代码会用线程池或进程池编写

2.6.2 submit()启用线程(进程)池:
(1)创建线程池:实例化
(2)将任务放入线程
使用线程池,打印当前线程id:

增加时延占用线程

传参:

启用进程池:进程池的用法和线程池用法基本相同

2.6.3 result()获取任务结果
使用ret.result()获取返回结果,程序会同步阻塞,不再并发运行

使用列表接收返回对象,最后统一打印。程序恢复并发

也可以使用字典存储返回对象

2.6.4 map()取代for循环和submit的操作

传多个参数

可迭代对象使用列表

线程map

2.6.5 add_done_callback()回调函数
谁先执行完,先处理谁。处理速度最快,效率最高。但数据的顺序是混乱的

想要识别任务和结果的对应关系,可以给结果增加标识组成元组

实例:分析网页信息


执行结果:

2.7 协程
2.7.1 协程概念(重要)
对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。
1)协程是操作系统不可见的
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
协程的所有切换都基于用户,只有在用户级别能够感知到的IO才能用协程模块做切换来规避(例如:socket、请求网页的操作)。
一些和文件相关的IO操作,只有操作系统能够感知到,只能使用线程来规避

2)协程的优点:
减轻了操作系统的负担
造成线程很忙的现象,可以得到更多的cpu的执行权限(时间片)

3)进程、线程、协程的区别:
进程:数据隔离 数据不安全 操作系统级别 开销非常大 可以利用多核
线程:数据共享 数据不安全 操作系统级别 开销非常小 不能利用多核 对IO更敏感
协程:数据共享 数据安全(单线程) 用户级别 开销最小 不能利用多核 不能识别文件操作IO

4)实现协程的2个模块:
gevent模块和asyncio模块

2.7.2 gevent第三方模块(重要)
Gevent是一个第三方库。
gevent模块利用了greenlet底层模块来完成切换,加上自己的规避I/O功能
1)协程实现

主程序中添加IO,可以调用协程。但主程序IO中断时间较短,协程程序无法运行完成

增加IO时长或增加IO次数,可以使协程完成运行。

推荐:创建阻塞,直到协程执行完成

2)协程并发

阻塞多个指定的协程

2.7.3 猴子补丁
使用time.sleep()会影响协程的并发,同样socket、requests等模块执行时,都会遇到该问题。

1)解决time、socket、requests等模块不能并发问题:
在time模块之前导入monkey,并执行“monkey.patch_all()”

2)判断猴子补丁是否有效

源码中值为True的默认支持,没有列出来的不支持

总结:将IO操作写到函数里,然后将函数提交gevent。在需要使用函数结果的地方使用join()制造阻塞。

3)基于gevent协程和线程池,实现socket并发

4)4核cpu能处理的并发数:
进程数:5
线程:20
协程:500
最大并发数 = 5 * 20 * 500 = 5W,已经足够使用。

2.7.4asyncio 内置模块
asyncio模块是python原生的内置模块
asyncio模块利用了yield底层模块来完成切换,加上自己的规避I/O功能
两个关键字:
async(重要)
await(重要)
1)功能实现

2)实现并发

3)asyncio使用协程完成http访问

原文地址:https://www.cnblogs.com/bdzxh/p/14081330.html