Python异步asynico

1.定义

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

1.1 asyncio的工作流程

  • 定义/创建协程对象

  • 将协程转为task任务 - 定义事件循环对象容器

  • 将task任务扔进事件循环对象中触发

1.2 asyncio的工作原理

1.假设任务只有两个状态:

  • 预备状态,是指任务目前空闲,但随时待命准备运行。
  • 等待状态,是指任务已经运行,但正在等待外部的操作完成,比如 I/O 操作

2.在这种情况下,event loop 会维护两个任务列表,分别对应这两种状态;并且选取预备状态的一个任务使其运行,一直到这个任务把控制权交还给event loop为止

3.当任务把控制权交还给event loop 时,event loop会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看他们是否完成。

  • 如果完成,则将其放到预备状态的列表;
  • 如果未完成,则继续放在等待状态的列表。

而原先在预备状态列表的任务位置仍旧不变,因为它们还未运行。

5.当所有任务被重新放置在合适的列表后,新一轮的循环又开始了:event loop 继续从预备状态的列表中选取一个任务使其执行…如此周而复始,直到所有任务完成。

1.3 asyncio的应用场景

  • 如果是I/O bound,并且 I/O 操作很慢,需要很多任务/线程协同实现,那么使用 Asyncio 更合适。
  • 如果是I/O bound,但是 I/O 操作很快,只需要有限数量的任务/线程,x那么使用多线程就可以了。
  • 如果是CPU bound,则需要使用多进程来提高程序运行效率

2.创建协程

from collections.abc import Coroutine

async def hello(name):
    print('Hello,', name)

if __name__ == '__main__':
    # 生成协程对象,并不会运行函数内的代码
    coroutine = hello("World")

    # 检查是否是协程 Coroutine 类型
    print(isinstance(coroutine, Coroutine))  # True

3.常见概念

名称含义
event_loop 事件循环程序开启一个无限的循环,程序员会把一些函数(协程)注册到事件循环上。当满足事件发生的时候,调用相应的协程函数
coroutine 协程协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用
future 对象代表将来执行或没有执行的任务的结果。它和task上没有本质的区别
task 任务个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。Task 对象是 Future 的子类,它将 coroutine 和 Future 联系在一起,将 coroutine 封装成一个 Future 对象
async/await 关键字python3.5 用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口,其作用在一定程度上类似于yield

4.基本使用

import asyncio

async def hello(name):
    print('Hello,', name)

# 定义协程对象
coroutine = hello("World")

# 定义事件循环对象容器
loop = asyncio.get_event_loop()
# task = asyncio.ensure_future(coroutine)

# 将协程转为task任务
task = loop.create_task(coroutine)

# 将task任务扔进事件循环对象中并触发
loop.run_until_complete(task)

输出

Hello, World

5.绑定回调函数

import time
import asyncio


async def _sleep(x):
    time.sleep(2)
    return '暂停了{}秒!'.format(x)

def callback(future):
    print('这里是回调函数,获取返回结果是:', future.result())

coroutine = _sleep(2)
loop = asyncio.get_event_loop()
task = asyncio.ensure_future(coroutine)

# 添加回调函数
task.add_done_callback(callback)

loop.run_until_complete(task)

输出

这里是回调函数,获取返回结果是: 暂停了2秒!

6.协程中的并发

import asyncio


async def do_work(x):
    print("waiting", x)
    await asyncio.sleep(x)
    return f"done after {x}s "


loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(do_work(1)),
         asyncio.ensure_future(do_work(2)),
         ]
loop.run_until_complete(asyncio.gather(*tasks))
# loop.run_until_complete(asyncio.wait(tasks))
for task in tasks:
    print("Task rest", task.result())

说了这么多,现在,我们不妨来深入代码底层看看。有了前面的知识做基础,你应该很容易理解这两段代码。

迭代一:同步

import asyncio
 
async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')
 
async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')
 
async def main():
    print('before await')
    await worker_1()
    print('awaited worker_1')
    await worker_2()
    print('awaited worker_2')
 
%time asyncio.run(main())
 
########## 输出 ##########
 
before await
worker_1 start
worker_1 done
awaited worker_1
worker_2 start
worker_2 done
awaited worker_2
Wall time: 3 s

迭代二:异步

import asyncio
 
async def worker_1():
    print('worker_1 start')
    await asyncio.sleep(1)
    print('worker_1 done')
 
async def worker_2():
    print('worker_2 start')
    await asyncio.sleep(2)
    print('worker_2 done')
 
async def main():
    task1 = asyncio.create_task(worker_1())
    task2 = asyncio.create_task(worker_2())
    print('before await')
    await task1
    print('awaited worker_1')
    await task2
    print('awaited worker_2')
 
%time asyncio.run(main())
 
########## 输出 ##########
 
before await
worker_1 start
worker_2 start
worker_1 done
awaited worker_1
worker_2 done
awaited worker_2
Wall time: 2.01 s

不过,第二个代码,到底发生了什么呢?为了让你更详细了解到协程和线程的具体区别,这里我详细地分析了整个过程。步骤有点多,别着急,我们慢慢来看。

  1. asyncio.run(main()),程序进入 main() 函数,事件循环开启;
  2. task1 和 task2 任务被创建,并进入事件循环等待运行;运行到 print,输出 'before await'
  3. await task1 执行,用户选择从当前的主任务中切出,事件调度器开始调度 worker_1;
  4. worker_1 开始运行,运行 print 输出'worker_1 start',然后运行到 await asyncio.sleep(1), 从当前任务切出,事件调度器开始调度 worker_2;
  5. worker_2 开始运行,运行 print 输出 'worker_2 start',然后运行 await asyncio.sleep(2) 从当前任务切出;
  6. 以上所有事件的运行时间,都应该在 1ms 到 10ms 之间,甚至可能更短,事件调度器从这个时候开始暂停调度;
  7. 一秒钟后,worker_1 的 sleep 完成,事件调度器将控制权重新传给 task_1,输出 'worker_1 done',task_1 完成任务,从事件循环中退出;
  8. await task1 完成,事件调度器将控制器传给主任务,输出 'awaited worker_1',·然后在 await task2 处继续等待;
  9. 两秒钟后,worker_2 的 sleep 完成,事件调度器将控制权重新传给 task_2,输出 'worker_2 done',task_2 完成任务,从事件循环中退出;
  10. 主任务输出 'awaited worker_2',协程全任务结束,事件循环结束。

接下来,我们进阶一下。如果我们想给某些协程任务限定运行时间,一旦超时就取消,又该怎么做呢?再进一步,如果某些协程运行时出现错误,又该怎么处理呢?同样的,来看代码。

迭代三:

import asyncio
 
async def worker_1():
    await asyncio.sleep(1)
    return 1
 
async def worker_2():
    await asyncio.sleep(2)
    return 2 / 0
 
async def worker_3():
    await asyncio.sleep(3)
    return 3
 
async def main():
    task_1 = asyncio.create_task(worker_1())
    task_2 = asyncio.create_task(worker_2())
    task_3 = asyncio.create_task(worker_3())
 
    await asyncio.sleep(2)
    task_3.cancel()
 
    res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True)
    print(res)
 
%time asyncio.run(main())
 
########## 输出 ##########
 
[1, ZeroDivisionError('division by zero'), CancelledError()]
Wall time: 2 s

你可以看到,worker_1 正常运行,worker_2 运行中出现错误,worker_3 执行时间过长被我们 cancel 掉了,这些信息会全部体现在最终的返回结果 res 中。

不过要注意return_exceptions=True这行代码。如果不设置这个参数,错误就会完整地 throw 到我们这个执行层,从而需要 try except 来捕捉,这也就意味着其他还没被执行的任务会被全部取消掉。为了避免这个局面,我们将 return_exceptions 设置为 True 即可。

7.协程中的嵌套

import asyncio


async def do_work(x):
    print("waiting", x)
    await asyncio.sleep(x)
    return f"done after {x}s "


async def main():
    tasks = [asyncio.ensure_future(do_work(1)),
             asyncio.ensure_future(do_work(2)),
             ]
    done, pending = await asyncio.wait(tasks)
    for task in done:
        print("Task rest", task.result())


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

8.gather和wait的区别

接受的参数

wait

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

gather

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(*tasks))

返回的结果

await

dones, pendings = await asyncio.wait(tasks)

for task in dones:
    print('Task ret: ', task.result())

gather

results = await asyncio.gather(*tasks)

for result in results:
    print('Task ret: ', result)

控制功能

import asyncio
import random


async def coro(tag):
    await asyncio.sleep(random.uniform(0.5, 5))

loop = asyncio.get_event_loop()

tasks = [coro(i) for i in range(1, 11)]


# 【控制运行任务数】:运行第一个任务就返回
# FIRST_COMPLETED :第一个任务完全返回
# FIRST_EXCEPTION:产生第一个异常返回
# ALL_COMPLETED:所有任务完成返回 (默认选项)
dones, pendings = loop.run_until_complete(
    asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED))
print("第一次完成的任务数:", len(dones))


# 【控制时间】:运行一秒后,就返回
dones2, pendings2 = loop.run_until_complete(asyncio.wait(pendings, timeout=1))
print("第二次完成的任务数:", len(dones2))


# 【默认】:所有任务完成后返回
dones3, pendings3 = loop.run_until_complete(asyncio.wait(pendings2))

print("第三次完成的任务数:", len(dones3))

loop.close()

9.动态添加协程

import time
import asyncio
from queue import Queue
from threading import Thread


async def do_work(x, queue: Queue, msg=""):
    await asyncio.sleep(x)
    queue.put(msg)


def start_loop(loop: asyncio.ProactorEventLoop):
    asyncio.set_event_loop(loop)
    loop.run_forever()


if __name__ == '__main__':
    queue = Queue()
    loop = asyncio.new_event_loop()

    my_thread = Thread(target=start_loop, args=(loop,))
    my_thread.start()
	#不能加join否者不能执行
    print(time.ctime())

    asyncio.run_coroutine_threadsafe(do_work(1, queue, "第一个"), loop)
    asyncio.run_coroutine_threadsafe(do_work(2, queue, "第二个"), loop)
    
    while True:
        msg = queue.get()
        print(f"{msg} 协程运行完成...")
        print(time.ctime())

10.队列实现动态任务添加

import asyncio
import time
from queue import Queue
from threading import Thread

QUEUE_PRODUCER = Queue()
QUEUE_CONSUMER = Queue()


def start_loop(loop):
    asyncio.set_event_loop(loop)
    loop.run_forever()


async def do_sleep(x, queue: Queue):
    await asyncio.sleep(x)
    queue.put('ok')


def start_producer():
    QUEUE_PRODUCER.put(5)
    QUEUE_PRODUCER.put(3)
    QUEUE_PRODUCER.put(1)


def consumer():
    while True:
        task = QUEUE_PRODUCER.get()
        if not task:
            time.sleep(1)
            continue
        asyncio.run_coroutine_threadsafe(do_sleep(int(task), QUEUE_CONSUMER), new_loop)


if __name__ == '__main__':
    print(time.ctime())
    new_loop = asyncio.new_event_loop()
    loop_thread = Thread(target=start_loop, args=(new_loop,))
    loop_thread.daemon = True
    loop_thread.start()

    start_producer()
    consumer_thread = Thread(target=consumer)
    consumer_thread.daemon = True
    consumer_thread.start()

    while True:
        msg = QUEUE_CONSUMER.get()
        print("协程运行完成..")
        print("当前时间:", time.ctime())

11.生产者消费者模型

到这里,发现了没,线程能实现的,协程都能做到。那就让我们温习一下这些知识点,用协程来实现一个经典的生产者消费者模型吧。

import asyncio
import random
 
async def consumer(queue, id):
    while True:
        val = await queue.get()
        print('{} get a val: {}'.format(id, val))
        await asyncio.sleep(1)
 
async def producer(queue, id):
    for i in range(5):
        val = random.randint(1, 10)
        await queue.put(val)
        print('{} put a val: {}'.format(id, val))
        await asyncio.sleep(1)
 
async def main():
    queue = asyncio.Queue()
 
    consumer_1 = asyncio.create_task(consumer(queue, 'consumer_1'))
    consumer_2 = asyncio.create_task(consumer(queue, 'consumer_2'))
 
    producer_1 = asyncio.create_task(producer(queue, 'producer_1'))
    producer_2 = asyncio.create_task(producer(queue, 'producer_2'))
 
    await asyncio.sleep(10)
    consumer_1.cancel()
    consumer_2.cancel()
    
    await asyncio.gather(consumer_1, consumer_2, producer_1, producer_2, return_exceptions=True)
 
%time asyncio.run(main())
 
########## 输出 ##########
 
producer_1 put a val: 5
producer_2 put a val: 3
consumer_1 get a val: 5
consumer_2 get a val: 3
producer_1 put a val: 1
producer_2 put a val: 3
consumer_2 get a val: 1
consumer_1 get a val: 3
producer_1 put a val: 6
producer_2 put a val: 10
consumer_1 get a val: 6
consumer_2 get a val: 10
producer_1 put a val: 4
producer_2 put a val: 5
consumer_2 get a val: 4
consumer_1 get a val: 5
producer_1 put a val: 2
producer_2 put a val: 8
consumer_1 get a val: 2
consumer_2 get a val: 8
Wall time: 10 s
  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值