Python异步编程(新手)

    科技2024-06-23  80

    文章目录

    异步1、协程定义(微线程) 2、实现协程2.1 asyncio定义执行方法执行顺序 2.2 async&await关键字 3、意义总结案例 4. 异步编程4.1 事件循环4.2 运行协程4.3 await案例1案例2 4.4 Task对象案例1案例2 4.5 Future对象4.6 异步上下文管理器4.7 uvloop 5. 实战案例爬虫


    异步

    1、协程

    协程不是计算机提供的,是人为创造的。

    定义(微线程)

    用一个线程在代码之间切换执行(是一种用户态内的上下文切换技术) def fun1(): print(1) ... print(2) def fun2(): print(3) ... print(4) func1() func2() 正常情况 顺序执行执行完func1后再执行func2 异步 一个线程可以实现在代码间来回切换比如可能先输出1,再输出3,返回去再输出2可以人为的控制

    2、实现协程

    greenlet:早期模块yield:生成器asyncio装饰器async、await关键词(主流:python3.5)

    2.1 asyncio

    @asyncio.coroutine def func1(): print(1) # 遇到IO耗时操作,自动切换到tasks中的其他任务 yield from asyncio.sleep(2) print(2) @asyncio.coroutine def func2(): print(3) # 遇到IO耗时操作,自动切换到tasks中的其他任务 yield from asyncio.sleep(2) print(4)
    定义
    一个普通函数加上装饰器(@asyncio.coroutine)后就是定义为协程函数 coroutine:协程的意思
    执行方法
    与普通函数直接调用执行不同协程函数的执行方法 定义一个looploop = asyncio.get_event_loop()然后把异步函数扔进去里面loop.run_until_complete(异步函数)多个异步函数时,可以定义一个tasks 将两个异步函数放在tasks列表里loop中传入tasks即可 tasks = [ asyncio.ensure_future(func1()), asyncio.ensure_future(func2()), ] loop = asyncio.get_event_loop() loop.run_until_complete(asyncio.wait(tasks))
    执行顺序
    只有一个线程执行(随机先执行某个异步函数)假设先执行func1()当一个线程在执行协程内容的时候,遇到yield,就会等待X秒但在等待的过程中,就会切换到其他的协程函数去执行在func2()中也遇到asyncio.sleep()的话,就两个异步函数都会进入等待状态只有等到某一个异步函数的asyncio.sleep过去后,才会继续执行输出2,func1()执行完后再继续执行func2(),输出4

    2.2 async&await关键字

    async def func1(): print(1) # 遇到IO耗时操作,自动切换到tasks中的其他任务 await asyncio.sleep(2) print(2) async def func2(): print(3) # 遇到IO耗时操作,自动切换到tasks中的其他任务 await asyncio.sleep(2) print(4) 与asyncio的差别:就是代码更加简洁

    3、意义

    在一个线程中如果遇到IO等待时间,线程不会傻傻等,利用空闲的时间再去干点其他事,不让线程闲着

    总结

    遇到IO阻塞自动切换

    网络IO请求:下载图片、访问网站适用于爬虫,就是在下载或者访问的等待过程(等待下载好、网站给我们的响应)可以执行其他异步函数,让他们也都发送请求,不等网站传回给我们响应,几秒后下载完成或者访问成功再返回去执行正常流程:(同步) 发送请求,等待2秒后,下载完成,保存执行下一个,发送请求,也是等待2秒 协程流程:(异步) 发送请求,需要等待2秒马上切换到另一个函数发送请求2秒后,第一个异步函数下载完成,保存,同时,第二个异步函数也下载完成 耗时比较(大概) 正常流程:2S+2S异步:2S反正就是快很多,如果函数更多,io等待时间长,那异步的优势就更大了

    案例

    4. 异步编程

    4.1 事件循环

    理解成为一个死循环,去检测并执行某些代码

    # 伪代码 任务列表 = [任务1,任务2,任务3...] # 在这个死循环里,会一直去检测每个任务的状态 # 任务状态分为:可执行和已完成两种 # 例如:任务1,任务2为可执行状态 # 则把任务1、2放进可执行的任务列表,把任务3放进已完成的任务列表 while True: 可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将’可执行‘和’已完成‘的任务返回 # 然后去遍历可执行的任务列表,执行里面的就绪任务 for 就绪任务 in 可执行的任务列表: 执行已就绪的任务 # 遍历已完成的任务列表,将已完成的任务从任务列表中去除 for 已完成的任务 in 已完成的任务列表: 在任务列表中移除已完成的任务 直到任务列表中的所有任务都完成了,即没有任务了,就终止循环 # 其中会遇到不可执行任务,就是wait io,这时在任务列表中就暂时忽略掉,先去执行可执行的 # 一直死循环,直到不可执行的任务也变成可执行状态 生成 import asyncio # 去生成或获取一个事件循环 # 这个loop就是上面那种死循环 loop = asyncio.get_event_loop() # 将任务扔到这个死循环里 loop.run_until_complete(任务列表)

    4.2 运行协程

    协程函数:定义函数 async def 函数名()协程对象:执行协程函数()得到的协程对象 async def func(): pass result = func()

    注意:执行协程函数创建协程对象,该函数内部代码不会执行。

    运行协程函数 将协程函数、协程对象以及事件循环搭配起来,才能运行就是将协程对象交给事件循环来处理 # 协程函数 async def func(): print('运行起来') # 协程对象 result = func() # 事件循环:把协程对象放进死循环里 # loop = asyncio.get_event_loop() # loop.run_until_complete(result) # 上面的两句可以合成一行代码:简便写法 asyncio.run(协程对象) #py3.7

    4.3 await

    await + 可等待的对象(协程对象、Future、Task对象)----> IO等待

    案例1
    import asyncio async def func(): print('开始') # response就是iO等待结束后的返回值 response = await asyncio.sleep(2) print('结束') asyncio.run(func())

    在整个过程中线程不会去等待它的睡眠,而是一直在死循环遍历任务列表,当协程对象的状态从不可执行变为可执行时才会执行下面的代码

    案例2
    import asyncio async def others(): print('开始') await asyncio.sleep(2) print('结束') return '返回值' async def func(): print('最先执行') # 遇到IO操作挂起当前协程(任务),等IO操作完成之后 # 也就是只有等到IO的函数也执行完了(不管里面有没有其他await),才继续往下执行。 # await就是切换到后面的任务,等任务执行完,有返回值了,才会继续 # 当前协程挂起时,事件循环可以去执行其他协程(任务) response = await others() print response asyncio.run(func())

    整个过程就是先输出:最先执行,然后输出:开始,等待2S后,输出结束,最后输出返回值

    4.4 Task对象

    在循环事件中添加多个任务

    任务列表 = 【任务1,任务2,任务3…】Tasks 用于并发调度协程 就是说任务列表中有很多个任务当有任务遇到IO等待时,就切换执行其他的任务 创建Tasks 通过asyncio.create_task(协程对象)的方式创建Task对象 执行Tasks await asyncio.wait(tasks)asyncio.wait 源码内部会对列表中的每个协程执行ensure_future从而封装为Task对象,所以在和wait配合使用时task_list的值为[func(),func()] 也是可以的。
    案例1
    import asyncio async def func(): print(1) await asyncio.sleep(2) print(2) return "返回值" async def main(): print("main开始") # 创建协程,将协程封装到Task对象中并添加到事件循环的任务列表中,等待事件循环去执行(默认是就绪状态)。 # 在调用 task_list = [ asyncio.create_task(func(), name="n1"), asyncio.create_task(func(), name="n2") ] print("main结束") # 当执行某协程遇到IO操作时,会自动化切换执行其他任务。 # 此处的await是等待所有协程执行完毕,并将所有协程的返回值保存到done # 如果设置了timeout值,则意味着此处最多等待的秒,完成的协程返回值写入到done中,未完成则写到pending中。 done, pending = await asyncio.wait(task_list, timeout=None) print(done, pending) asyncio.run(main())
    案例2
    import asyncio async def func(): print("执行协程函数内部代码") # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。 response = await asyncio.sleep(2) print("IO请求结束,结果为:", response) coroutine_list = [func(), func()] # 错误:coroutine_list = [ asyncio.create_task(func()), asyncio.create_task(func()) ] # 此处不能直接 asyncio.create_task,因为将Task立即加入到事件循环的任务列表, # 但此时事件循环还未创建,所以会报错。 # asyncio.wait内部会对列表中的每个协程执行ensure_future,封装为Task对象。 done,pending = asyncio.run( asyncio.wait(coroutine_list) )

    4.5 Future对象

    不常用,更底层,是Task的基类

    4.6 异步上下文管理器

    pass

    4.7 uvloop

    pip install uvloop import asyncio import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # 编写asyncio的代码,与之前写的代码一样 # 内部的事件循环自动化会变为uvloop asyncio.run(....)

    5. 实战案例

    爬虫

    在这里插入代码片
    Processed: 0.010, SQL: 9