进程
- 为什么要使用多进程?——GIL的存在,多线程实际不是并发执行
将任务分为两类: - IO密集型(多线程)
- CPU密集型(多进程)
多进程的基本用法
concurrent.futures.process.ProcessPoolExecutor#进程池 (与线程池用法一致
multiprocessing.Pool#单个进程
multiprocessing.Pool #进程池
可直接学习复习该链接:https://blog.csdn.net/ifhuke/article/details/128642625
有进程间通信,进程池
创建进程
委托操作系统创建,但不同操作系统创建进程的方式不一样
- linux:
fork
:从父进程中继承内存(从运行了一半的程序中裂变出去) - windows:
spawn
:重新运行Python程序(1. 启动python程序; 2.import代码文件
协程
一种用户态的上下文切换技术(通过一个线程实现代码块间的相互切换执行,在一个协程中,遇到io等待时间,可以利用这个等待时间去做其他事情
yield
就是一个协程思想的实现
yield、yield from
yield
可以将函数变成生成器
在函数中加yield
关键字,调用它,就会返回一个生成器
生成器的目的不是为了将代码打包,目的是为了生产或者返回一些值
1 创造生成器
2 通过next
获取下一个值(可以理解为next去取yield的值)
3 如果遇到了StopIteration
,说明内容生成结束
以上的用法,也叫做迭代器协议,就是不断从中去取内容,可以被for
去使用
通过yield
可以改变程序执行顺序,因此协程也称之为“用户态的线程”或“微线程”,即程序执行顺序是受用户控制的。
在Python中,协程是基于单线程的。
Tornado是一个Web开发框架,内部使用协程提供高并发
通过主线程去调度,即从g2中获得i,给到g3,再得到值,改进这个情况,引入yield from
yield是将生成器作为结果返回,yield from是把生成器的结果作为结果返回。但后来不用这个写法了,就先pass
协程的相关概念
- 协程函数:定义为
async def
的函数 - 协程对象:调用协程函数返回的对象
- 事件循环:event loop 并发执行任务的大脑,判断哪些任务已处于可执行状态,并执行(循环只是主线程不断从事件队列里面取值/函数的过程)
- 协程任务:rask 事件循环调度的最小单位,可由协程对象转化
关键字概述
async
定义函数时加上async
,则该函数为协程函数await
后面是一个可等待对象,如协程对象,协程任务---->去运行这个函数,运行完再继续fanhuiasyncio
编写并发代码的库run
运行最高层级的入口点asyncio.create_task()
创建task对象
import asyncio
import time
async def async_test(delay:int,content):await asyncio.sleep(delay)print(content)async def main():task_lady = asyncio.create_task(async_test(1,"lady"))#创建tasktask_killer = asyncio.create_task(async_test(2,"killer9"))await task_killer#可以await所有任务,如果只await了耗时短的,那么其他任务没有完成就结束了if __name__ == '__main__':print(f"start at {time.strftime('%X')}")asyncio.run(main())print(f"end at {time.strftime('%X')}")
gather
接收列表,结果是一个由所有返回值聚合而成的列表。结果值得顺序与was
生成器如何使用协程
借助工具实现协程,在这些工具中,使用yield
关键字
- Tornado提供了各种协程版的函数
- 使用gevent的猴子补丁,可以不使用协程的写法,但是有协程的开发效果
asyncio模块
用来编写并发代码的酷,被用作多个提供高性能Python异步框架的基础,包括网络和网站服务,数据库链接库,分布式任务队列等,asyncio
往往是构建IO密集型和高层级 结构化 网络代码的最佳选择
import time
import asyncioasync def f1():time.sleep(1)#这是线程阻塞的代码 整个线程会停住一秒print("f1:done")async def f2():time.sleep(1)#这是线程阻塞的代码print("f2:done")async def main():await f1();await f2();if __name__ == "__main__":time = time.time();asyncio.run(main());#入口time2 = time.time();print(f"耗时{time2-time:.2f} ");
耗时2s
用asyncio后改变的代码:
import time
import asyncioasync def f1():await asyncio.sleep(1)#这是非阻塞的协程 ,类似于yeildprint("f1:done")async def f2():asyncio.sleep(1)#非阻塞的协程 ,类似于yeildprint("f2:done")async def main():await f1();#等待协程结束await f2();
依然2s,依旧不是并行运行的,需要用到
修改代码:
async def main():await asyncio.gather(f1(),f2());
修改后,耗时1s,是并发操作
所以在异步编程中,如果有一处代码写成了同步,则整个代码都同步。
所以,像锁,sleep,迭代,队列,网络请求(socket)等都需要用异步资源
注意很多的第三方模块都是同步的,所以我们需要确定是“异步”的第三方库 https://github.com/aio-libs
async 和 await
创建协程的方式有两种,一种是yield
关键字,一种是async
关键字,我们一般用后者
定义函数时,加上async
修饰,即async def func()
,则该函数为协程函数,协程函数返回的对象即为协程对象
await
后面是一个可等待对象,如协程对象,协程任务,用于告诉eventloop在此协程中需要等待后面的函数运行完成后才能继续(即执行下一个协程),运行完成后返回结果
新的协程如何运行
- 在其他协程中被
await
- 使用
asyncio
模块提供第一个协程的运行
async def f_3():print("f3 doing")await f_4()#等f4async def f_4():print("f4 doing")if __name__=="__main__":asyncio.run(f_3())#第一个协程的运行
或者使用task
方法执行协程
并发方案的选择
1 分布式,还是单击
- 分布式,只能选择多进程
2 CPU密集,还是IO密集
- CPU密集,多进程
3 单击IO密集型,并发数多不多
- 如果不多,多线程(推荐)
- 如果多,协程
- 如果特别多:多进程+多线程+协程
4 是否有动态调度
- 没有, 线程池
- 有,手动创建线程
并发任务之间的通信:队列
并发任务之间的同步:锁