vlambda博客
学习文章列表

3-3,协程&asyncio&异步编程补充


  1. 协程

    - 不是计算机提供,程序员人为创造

    - 协程(coroutine),也可以成为微线程,是一种用户态内的上下文切换技术。

    - 简而言之,其实就是通过一个线程实现代码块相互切换执行。例如

def func1(): print(1) ... print(2)
def func2(): print(3) ... print(4)
func1()func2()

-实现协程有这么几种方法:

  -grenlet,早期模块

  -yield关键字

  -asyncio装饰器(py3.4

  -asyncawait关键字(py3.5)【推荐】

 1.1 greenlet实现协程

    - 示例

from greenlet import greenletdef func1(): print(1) gr2.switch() print(2) gr2.switch()
def func2(): print(3) gr1.switch() print(4)
gr1 = greenlet(func1)gr2 = greenlet(func2)
gr1.switch()

输出

1324

1.2 yield关键字

    - 示例:

def func1(): yield 1 yield from func2() yield 2
def func2(): yield 3 yield 4
f1 = func1()for item in f1: print(item)

输出

1342
1.3 asyncio

- python3.4及以后的版本

  - 示例:

import asyncio
@asyncio.coroutinedef func1(): print("1") # 网络IO请求 下载一张图片 await asyncio.sleep(2) print("2")
@asyncio.coroutinedef func2(): print("3") # 网络IO请求 下载一张图片 await asyncio.sleep(2) print("4")
tasks = { asyncio.ensure_future(func1()), asyncio.ensure_future(func2()) }
loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))

注意:遇到IO阻塞自动切换

输出

1324
 
1.4 async & await关键字

- py3.5之后的版本

  - 示例:

import asyncio
async def func1(): print("1") # 网络IO请求 下载一张图片 await asyncio.sleep(2) print("2")
async def func2(): print("3") # 网络IO请求 下载一张图片    await asyncio.sleep(2) print("4")
tasks = { asyncio.ensure_future(func1()), asyncio.ensure_future(func2())}
loop = asyncio.get_event_loop()loop.run_until_complete(asyncio.wait(tasks))

输出

1324

2. 协程的意义

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

    - 案例:去下载三张图片(IO)

    - 普通方式

import requestsimport timedef download_img(url):
print("开始下载:",url) response = requests.get(url) print("下载完成")
filename = url.split('/')[-1] with open(filename,'wb') as fp:        fp.write(response.content)        if __name__ == "__main__": start_time = time.time() url_list = [ 'http://pic.netbian.com/uploads/allimg/200629/230511-1593443111e579.jpg', 'http://pic.netbian.com/uploads/allimg/200411/212620-1586611580283a.jpg', 'http://pic.netbian.com/uploads/allimg/190925/201558-15694137581dc0.jpg' ] for item in url_list: download_img(item) end_time = time.time() t = end_time - start_time print("下载时间为:", t)

输出:

开始下载:http://pic.netbian.com/uploads/allimg/200629/230511-1593443111e579.jpg下载完成开始下载:http://pic.netbian.com/uploads/allimg/200411/212620-1586611580283a.jpg下载完成开始下载:http://pic.netbian.com/uploads/allimg/190925/201558-15694137581dc0.jpg下载完成下载时间为:1.0819127559661865

    - 协程方式:

import aiohttpimport asyncioimport time
async def fetch(session,url): print("发送请求",url) async with session.get(url,verify_ssl=False) as response: content = await response.content.read() file_name = url.split("/")[-1] with open(file_name, 'wb') as fp: fp.write(content)
async def main(): async with aiohttp.ClientSession() as session: start_time = time.time() url_list = [ 'http://pic.netbian.com/uploads/allimg/200629/230511-1593443111e579.jpg', 'http://pic.netbian.com/uploads/allimg/200411/212620-1586611580283a.jpg', 'http://pic.netbian.com/uploads/allimg/190925/201558-15694137581dc0.jpg' ] tasks = [asyncio.create_task(fetch(session,url)) for url in url_list] await asyncio.wait(tasks)
end_time = time.time() t = end_time - start_time print("下载时间为:", t)
if __name__ == "__main__": asyncio.run(main()) print("下载完成")

输出

发送请求 http://pic.netbian.com/uploads/allimg/200629/230511-1593443111e579.jpg发送请求 http://pic.netbian.com/uploads/allimg/200411/212620-1586611580283a.jpg发送请求 http://pic.netbian.com/uploads/allimg/190925/201558-15694137581dc0.jpg下载时间为:0.7550625801086426下载完成

3.异步编程

3.1 事件循环

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

    - 伪代码

任务列表 = [任务1,任务2,任务3.。。]
while True 可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将可执行和已完成的任务返回
for 就绪任务 in 已就绪的任务列表: 执行已就绪的任务
for 已完成任务 in 已完成的任务列表: 在任务列表中移除 已完成的任务
    如果任务列表中的任务已完成 终止循环
import asyncio
# 去生成一个事件循环loop = asyncio.get_event_loop()# 将任务放到任务列表loop.run_until_complete(asyncio.wait(tasks))

3.2  快速上手

    - 协程函数,定义函数时,asyncio def 函数名。

    - 协程对象,执行协程函数()得到的协程对象。

import asyncio
async def func(): pass
result = func() # result就是一个协程对象注意:执行协程函数创建协程对象,函数内部代码不会执行如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理

    - 示例:

import asyncio
async def func():    print("你好呀")    result = func()
# loop = asyncio.get_event_loop()# loop.run_until_complete(result)asyncio.run(result)#py3.7之后

输出

你好

3.3  await

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

    - 示例1

import asyncio
async def func(): print("你好呀!") response = await asyncio.sleep(2) print("结束",response)
asyncio.run(func())

输出

你好呀!结束 None

    - 示例2:

import asyncio
async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值'
async def func(): print("执行协程函数内部代码")
# 遇到IO操作挂起当前协程,等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务) response = await others()
    print("IO请求结束,结果为:",response)
asyncio.run(func())

输出

执行协程函数内部代码startendIO请求结束,结果为:返回值

-  示例3:(可以接多个await)

import asyncio
async def others(): print("start") await asyncio.sleep(2) print('end') return '返回值'
async def func(): print("执行协程函数内部代码")
response1 = await others()
print("IO请求结束,结果为:",response1)
response2 = await others()
print("IO请求结束,结果为:", response2)
asyncio.run(func())

    - await就是等待对象的值得到结果之后再继续往下走

输出

执行协程函数内部代码startendIO请求结束,结果为:返回值startendIO请求结束,结果为:返回值

3.4 Task对象

    - 白话:在事件循环中添加多个任务

    - 注意:asyncio.create_task(协程对象)函数在python3.7中被加入,3.7之前可以用低层级的asyncio.ensure_future()函数

    - 示例1:

import asyncioimport time
async def func(): print("1") await asyncio.sleep(2) print('2') return '返回值'
async def main(): start = time.time() print("main开始")
#创建task对象,将当前执行func函数任务添加到事件循环 task1 = asyncio.create_task(func())
task2 = asyncio.create_task(func())
print("main结束")
ret1 = await task1 ret2 = await task2 print(ret1,ret2) end = time.time() print(end - start)
asyncio.run(main())

执行结果为

main开始main结束1122返回值 返回值2.0011146068573

    - 示例2:常用写法

#上面代码的改进写法,常用写法import asyncio
async def func(): print("1") await asyncio.sleep(2) print('2') return '返回值'
async def main(): print("main开始")
task_list = [ asyncio.create_task(func(),name="n1"), asyncio.create_task(func(),name="n2") ]
print("main结束")
# await 后不能直接加task_list,将多个task对象加到协程中 # done:已完成的任务返回值都会返回到done中,是个集合,元素即为返回值 # pending即为还没完成的任务 done,pending = await asyncio.wait(task_list,timeout=None) print(done)
asyncio.run(main())

执行结果为

main开始main结束1122{<Task finished name='n2' coro=<func() done, defined at E:/pywork/小猿圈爬虫课程(2020爬虫全套教程)/协程&asyncio&异步/test.py:201> result='返回值'>, <Task finished name='n1' coro=<func() done, defined at E:/pywork/小猿圈爬虫课程(2020爬虫全套教程)/协程&asyncio&异步/test.py:201> result='返回值'>}

    - 示例3:(task_list在asyncio之前)

import asyncio
async def func(): print("1") await asyncio.sleep(2) print('2') return '返回值'
task_list = [ asyncio.create_task(func(),name="n1"), asyncio.create_task(func(),name="n2") ] done,pending = asyncio.run(asyncio.wait(task_list))print(done)# 会报错,在task_list位置未创建事件循环

应改成

import asyncio
async def func(): print("1") await asyncio.sleep(2) print('2') return '返回值'
#写在外边不应设置成Task对象,未有事件循环task_list = [ func(), func() ]
# asyncio.run内部会创建事件循环 done,pending = asyncio.run(asyncio.wait(task_list))print(done)

输出结果为

1122{<Task finished name='Task-3' coro=<func() done, defined at E:/pywork/小猿圈爬虫课程(2020爬虫全套教程)/协程&asyncio&异步/test.py:229> result='返回值'>, <Task finished name='Task-2' coro=<func() done, defined at E:/pywork/小猿圈爬虫课程(2020爬虫全套教程)/协程&asyncio&异步/test.py:229> result='返回值'>}

    - task对象就是将某个任务放到事件循环,并发的创建多个任务,让事件循环遇到IO自由切换处理。

3.5 asyncio.Future对象(更底层,不常用,要理解)

    - Task继承Future,Task对象内部await结果的处理基于Future对象来的

    - 示例1:

async def main():  # 获取当前事件循环 loop = asyncio.get_running_loop()  #创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束 fut = loop.create_future()     #等待任务最终结果(Future对象),没有结果则会一直等待下去 await fut
asyncio.run(main())

    - 示例2:

async def set_affer(fut): await asyncio.sleep(2) fut.set_result("666")
async def main(): # 获取当前事件循环 loop = asyncio.get_running_loop() # 创建一个任务(Future对象),没绑定任何行为,则这个任务永远不知道什么时候结束 fut = loop.create_future()     #创建一个任务(Task对象),绑定了set_after函数,函数内部在2s以后,会给fut赋值    #即手动设置future任务的最终结果,那么fut就可以结束了 await loop.create_task(set_affer(fut))     #等待Future对象获取最终结果,否则会一直等待下去 data = await fut print(data)
asyncio.run(main())

输出结果为

666
本篇是根据B站up主:路飞学城IT上传的“2020年Python爬虫全套课程(学完可做项目)”编写