Loading

Python协程和异步IO

同步和异步的概念

同步:是指代码调用IO操作时,必须等待IO操作完成后才返回的调用方式。

异步:是指代码调用IO操作时,不必等待IO操作完成就可以返回的调用方式。

协程

协程(coroutine)又称微线程,是运行在单线程中的“并发”,可以理解为轻量级的线程,对于频繁进行IO操作的任务,一般选择使用协程。

协程相对于线程而言切换的开销更小,是由程序自身控制的。协程的本质是单线程执行,要想利用多核CPU,可以在程序中开启多进程,每个进程内再开启多个线程,每个线程内再开启协程。这样既可以充分利用CPU,又能发挥协程的高效率,可获得极高的性能。

Python中协程的发展:

  • Python2中使用 yield+send()语法。
  • Python3.3中引入 yield from 可以接收返回值。
  • Python3.4中引入了 asyncio 模块,直接内置了对异步IO的支持。
  • Python3.5增加了 asyncawait 关键字。
  • Python3.7使用 async def + await 的方式定义协程。

asyncio

asyncio模块提供了是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

官方文档:https://docs.python.org/zh-cn/3/library/asyncio-task.html

核心概念

asyncio核心组件包括事件循环(Event Loop)、协程(Coroutine)、任务(Task)和未来对象(Future)等。

  • 事件循环(Event Loop):在整个程序运行过程中不断循环执行,相当于无限循环,可以把一些函数注册到事件循环上,当满足条件时调用相应的协程来处理这些事件。
  • 协程(Coroutine):协程对象,指的是使用 async 关键字定义的方法,它的调用不会被立即执行,而是会返回一个协程对象。协程对象需要注册到事件循环中,由事件循环调用。
  • 任务(Task):任务是对协程对象的进一步封装,包含了任务的各个状态,被用来在事件循环中运行协程。
  • 对象(Future):代表将来执行或没有执行的任务的结果,和Task没有本质上的区别。

基本示例

使用 asyncio 一般通过如下几步:

  • 创建协程对象
  • 定义事件循环
  • 将协程转为task任务
  • 将task任务加入到事件循环
import asyncio
import time

# 创建协程对象
async def get_info(name):
    print("start....")
    print(name)
    await asyncio.sleep(2)
    print("end...")

if __name__ == '__main__':
    start_time = time.time()

    # 定义事件循环
    loop = asyncio.get_event_loop()

    # 将协程转为task任务
    task = loop.create_task(get_info("hello, world"))

    # 将task任务加入到事件循环中触发
    loop.run_until_complete(task)
    
    # python3.7及之后
    # asyncio.run(task)

    print(time.time()-start_time)

注:以下两步,在Python3.7及之后可以使用 asyncio.run() 的方式

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

asyncio.create_task() 是 Python3.7 加入的更高级的API,在 Python3.6时,使用 asyncio.ensure_future() 来创建 Future,Future 也是一个管理协程运行状态的对象,与 Task 没有本质上的区别。

获取协程的返回值

方式1:通过 task.result()

import asyncio
import time

async def get_info(name):
    print("start...")
    await asyncio.sleep(2)
    return name

if __name__ == '__main__':
    start_time = time.time()
    loop = asyncio.get_event_loop()
    # get_future = asyncio.ensure_future(get_info("hello world"))
    # loop.run_until_complete(get_future)
    # print(get_future.result())

    task = loop.create_task(get_info("hello world"))
    loop.run_until_complete(task)
    print(task.result())
    print(time.time() - start_time)

方式2:通过 add_done_callback() 回调

import asyncio

async def get_info(name):
    print("start...")
    await asyncio.sleep(2)
    return name

def callback(future):
    print(future.result())

if __name__ == '__main__':
    loop = asyncio.get_event_loop()

    info = get_info("hello world")
    task = loop.create_task(info)

    task.add_done_callback(callback)
    loop.run_until_complete(task)

注:要想给callback函数添加参数:

from functools import partial
# 参数必须放在future前面
def callback(language, future):
    print(language)
    print(future.result())

...
task.add_done_callback(partial(callback, "python"))

协程的并发

通常需要将多个协程注册到事件循环中,并发的运行。主要有两种方式实现:

  • asyncio.gather(*tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(*tasks)) # 这里的*不能省略
  • asyncio.wait(tasks)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

gatherwait的区别:

  • gather 是更高层级的API,使用更加灵活,可以将任务分组,它会把值直接返回。
tasks1 = [work(num) for num in range(0, 3)]
tasks2 = [work(num) for num in range(3, 6)]
group1 = asyncio.gather(*tasks1)
group2 = asyncio.gather(*tasks2)
results1, results2 = await asyncio.gather(group1, group2)
print(results1, results2)
  • wait 会返回 donepending
    • done:表示已完成的任务
    • pending:表示未完成的任务
done, pending = await asyncio.wait(tasks)
for task in done:    
    print('Task Result: ', task.result())

示例:

import asyncio

async def get_info(name):
    print("start...")
    print(name)
    await asyncio.sleep(2)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    group1 = [get_info("Python") for i in range(3)]
    group2 = [get_info("Golang") for i in range(3)]
    loop.run_until_complete(asyncio.gather(*group1, *group2))

协程的嵌套

在一个协程中可以使用 await 调用另外一个协程。

import asyncio

async def func1(temp):
    print('start...', temp)
    await asyncio.sleep(2)
    print('end...', temp)
    return 'Hello Python'

async def main():
    coroutine1 = func1(1)
    coroutine2 = func1(2)
    coroutine3 = func1(3)

    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]
    # await 一个task列表
    done, pending = await asyncio.wait(tasks)

    for task in done:
        print('Task Result: ', task.result())

if __name__ == '__main__':
    asyncio.run(main())

常见的函数

call_soon:立即执行

import asyncio

def callback(sleep_times):
    print("sleep {} success".format(sleep_times))
def stoploop(loop):
    loop.stop()

if __name__ == "__main__":
    # 创建一个事件循环
    loop = asyncio.get_event_loop()
    # 立即启动callback函数
    loop.call_soon(callback, 2)
    # 执行完毕后,立即启动执行stoploop函数
    loop.call_soon(stoploop, loop)
    loop.run_forever()

call_later:设置一定时间启动执行

import asyncio


def callback(sleep_times):
    print("sleep {} success".format(sleep_times))

def stoploop(loop):
    loop.stop()

if __name__ == "__main__":
    # 创建一个事件循环
    loop = asyncio.get_event_loop()

    loop.call_later(2, callback, 2)
    loop.call_later(1, callback, 1)
    loop.call_later(3, callback, 3)

    loop.call_later(4, stoploop, loop)
    loop.run_forever()

call_at:类似于call_latercall_later内部其实调用了call_later,但是call_at指定的时间不是传统意义上的时间而是loop内部的时间。

import asyncio

def callback(sleep_times, loop):
    print("success time {}".format(loop.time()))

def stoploop(loop):
    loop.stop()

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    # loop内部的时间
    now = loop.time()
    
    loop.call_at(now+2, callback, 2, loop)
    loop.call_at(now+1, callback, 1, loop)
    loop.call_at(now+3, callback, 3, loop)

    loop.call_at(now+6, stoploop, loop)
    loop.run_forever()

call_soon_threadsafe:和 call_soon 用法一致,不过是线程安全的。

异步迭代器

  • 什么是异步迭代器:

实现了__aiter__()__anext__() 方法的对象,__anext__ 返回一个awaitable对象,async_for会处理异步迭代器的__anext__()方法所返回的可等待对象,直到触发StopAsyncIteration异常。

  • 什么是异步可迭代对象:

可在 async for 语句中被使用的对象,必须通过它的__aiter__() 方法返回一个 asynchronous iterator

import asyncio

class Reader(object):
    """ 自定义异步迭达器(同时也是异步可迭达对象) """

    def __init__(self):
        self.count = 0

    async def readline(self):
        await asyncio.sleep(1)
        self.count += 1
        if self.count == 100:
            return None
        return self.count

    def __aiter__(self):
        return self

    async def __anext__(self):
        val = await self.readline()

        if val is None:
            raise StopAsyncIteration
        return val

async def func():
    # async for 只能写在协程函数内
    async for item in Reader():
        print(item)

if __name__ == '__main__':
    asyncio.run(func())

异步上下文管理器

此种对象通过定义 __aenter__()__aexit__() 方法来对 async with语句中的环境进行控制。

import asyncio

class AsyncContextManager:
    def __init__(self):
        print("init...")

    async def do_something(self):
        # 异步操作数据库
        return 'xxx'

    async def __aenter__(self):
        print("aenter...")
        # 异步连接数据库
        self.conn = await asyncio.sleep(1)
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("aexit...")
        # 异步关闭数据库连接
        await asyncio.sleep(1)

async def func():
    # async with 必须嵌套在协程函数里面
    async with AsyncContextManager() as f:
        result = await f.do_something()
        print(result)

if __name__ == '__main__':
    asyncio.run(func())
posted @ 2021-07-05 09:58  charlatte  阅读(116)  评论(0编辑  收藏  举报