协程

  • 参考网址
- https://www.cnblogs.com/wupeiqi/p/12834355.html
  • 不是计算机提供,程序员人为创造
  • 也称为微线程,是一种上下文切换技术(通过一个线程实现代码块互相切换执行)
  • 普通代码的执行流程自上而下顺序执行
def fun1():
    print(1)
    # ...
    print(2)

def fun2():
    print(3)
    # ...
    print(4)

fun1()
fun2()

- 结果: 等fun1执行完,再执行fun2
  • 实现方法

    • greenlet: 早期模块
    • yield 关键字
    • asyncio装饰器(py3.4开始支持)
    • async,await关键字(py3.5开始支持,推荐)
  • greenlet实现协程

from greenlet import greenlet

def fun1():
    print(1)
    gr2.switch() # 跳转到fun2
    print(2)
    gr2.switch()

def fun2():
    print(3)
    gr1.switch() # 跳转到fun1
    print(4)

gr1 = greenlet(fun1)
gr2 = greenlet(fun2)

gr1.switch() # 开始执行

- 返回结果:
	1
    3
    2
    4

  • yield示例
def fun1():
    yield 1
    yield from fun2() # 切换到 fun2
    yield 2

def fun2():
    yield 3
    yield 4

f1 = fun1()
for item in f1:
    print(item)
    
- 返回结果:
	1
    3
    4
    2
  • asyncio示例
import asyncio


# 先定义两个协程对象
@asyncio.coroutine
def fun1():
    print(1)
    # 这里不能直接 asyncio.sleep(2)(报错信息: coroutine 'sleep' was never awaited)
    yield from asyncio.sleep(2)
    print(2)
    
@asyncio.coroutine
def fun2():
    print(3)
    yield from asyncio.sleep(2)
    print(4)

# 创建任务
tasks = [
    asyncio.ensure_future(fun1()),
    asyncio.ensure_future(fun2())
]

# 创建'轮询对象'并执行任务
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
  • async & await示例
import asyncio

async def fun1(): # 协程函数对象
    print(1)
    await asyncio.sleep(2) # 等的时候切换到其他代码块并执行
    print(2)

async def fun2():
    print(3)
    await asyncio.sleep(2)
    print(4)

tasks = [
    asyncio.ensure_future(fun1()),
    asyncio.ensure_future(fun2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

- 结果:

	1
    3
    2
    4

  • 非异步协程下载图片示例
import requests

url_list = [
    'https://www.aclas.com/uploadfile/uploadfile/225986195944098.png',
    'https://www.aclas.com/uploadfile/uploadfile/26507715014500.jpg',
    'https://www.aclas.com/uploadfile/uploadfile/34698673154118.jpg',
]


def download_image(url):
    filename = url.rsplit('/')[-1]
    res = requests.get(url)
    with open(filename, 'wb') as file:
        file.write(res.content)
    print(filename + '下载完成')

if __name__ == '__main__':
    for url in url_list:
        download_image(url)

  • 异步协程下载图片示例
import aiohttp
import asyncio

url_list = [
    'https://www.aclas.com/uploadfile/uploadfile/225986195944098.png',
    'https://www.aclas.com/uploadfile/uploadfile/26507715014500.jpg',
    'https://www.aclas.com/uploadfile/uploadfile/34698673154118.jpg',
]

# 下载图片
async def download_image(request,url):
    print('开始发送请求',url)
    async with request.get(url,verify_ssl=False) as res:
        content = await res.content.read()
        filename = url.rsplit('/')[-1]
        with open(filename, 'wb') as file:
            file.write(content)
        print(filename + '下载完成')
        
# 创建请求对象并创建任务,等待任务执行完成
async def main():
    async with aiohttp.ClientSession() as request:
        tasks = [asyncio.create_task(download_image(request,url)) for url in url_list]
        await asyncio.wait(tasks)
    
    
if __name__ == '__main__':
    # asyncio.run(main()) 这种写法会报错...
    loop = asyncio.get_event_loop() # 创建'轮询对象'并开始执行
    loop.run_until_complete(main())

异步协程

  • 参考网址
- https://www.cnblogs.com/wupeiqi/p/12834355.html

意义

- 计算型的操作,利用协程来回切换执行,没有任何意义,来回切换并保存状态 反倒会降低性能。
- IO型的操作,利用协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能,从而实现异步编程(不等待任务结束就可以去执行其他代码)。

底层原理,伪代码演示

  • 事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环
# 伪代码
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
    可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
    for 就绪任务 in 已准备就绪的任务列表:
        执行已就绪的任务
    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除 已完成的任务
    如果 任务列表 中的任务都已完成,则终止循环

开始

  • 必须创建一个轮询对象
import asyncio
loop = asyncio.get_event_loop() # 创建轮询对象
  • 定义协程函数
# 定义一个协程函数
async def func():
    pass
# 调用协程函数,返回一个协程对象
# 注意事项: 调用协程函数时,函数内部代码不会执行,只是会返回一个协程对象
result = func() 
  • 基本应用: 事件循环协程对象 配合才能实现
import asyncio
async def func():
    print("协程内部代码")
# 调用协程函数,返回一个协程对象。
result = func()
# 方式一
# loop = asyncio.get_event_loop() # 创建一个事件循环
# loop.run_until_complete(result) # 将协程当做任务提交到事件循环的任务列表中,协程执行完成之后终止。
# 方式二
# 本质上方式一是一样的,内部先 创建事件循环 然后执行 run_until_complete,一个简便的写法。
# asyncio.run 函数在 Python 3.7 中加入 asyncio 模块,
asyncio.run(result)
  • await关键字: 是一个只能在协程函数中使用的关键字
- 用于遇到IO操作时挂起 当前协程(任务),当前协程(任务)挂起过程中 事件循环可以去执行其他的协程(任务),当前协程IO处理完成时,可以再次切换回来执行await之后的代码
import asyncio
async def func():
    print("执行协程函数内部代码")
    # 遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。
    # 当前协程挂起时,事件循环可以去执行其他协程(任务)。
    response = await asyncio.sleep(2)
    print("IO请求结束,结果为:", response)
result = func()
asyncio.run(result)