所谓「异步 IO」,就是你发起一个 IO 操作,却不用等它结束,你可以继续做其他事情,当它结束时,你会得到通知。

Asyncio 是并发(concurrency)的一种方式。当然对 Python 来说,并发编程还可以通过线程(threading)和多进程(multiprocessing)来实现。Asyncio 并不能带来真正的并行(parallelism)。

当然,因为 GIL(全局解释器锁)的存在,Python 的多线程也不能带来真正的并行。

可交给 asyncio 执行的任务,称为协程(coroutine)。一个协程可以放弃执行,把机会让给其它协程(即 yield from 或 await)。

python协程多请求同时并发 python 协程 并发_python


关于并发入门的博客:唤醒手腕Python全栈工程师学习笔记(并发编程篇)

协程编程基本介绍

协程的定义,需要使用 async def 语句

async def do_some_work(x): pass

do_some_work 便是一个协程。准确来说,do_some_work 是一个协程函数,可以通过 asyncio.iscoroutinefunction 来验证:

print(asyncio.iscoroutinefunction(do_some_work)) # True

这个协程什么都没做,我们让它睡眠几秒,以模拟实际的工作量 :

import asyncio

async def do_some_work(time):
    print("Waiting " + str(time))
    await asyncio.sleep(time)

这边注意下,要使用的是asyncio提供的sleep方法,不能使用time.sleep(),因为time.sleep()是同步阻塞的。

在解释 await 之前,有必要说明一下协程可以做哪些事。协程可以:

  1. 等待一个 future 结束
  2. 等待另一个协程(产生一个结果,或引发一个异常)
  3. 产生一个结果给正在等它的协程
  4. 引发一个异常给正在等它的协程

asyncio.sleep 也是一个协程,所以 await asyncio.sleep(arg) 就是等待另一个协程。可参见 asyncio.sleep 的文档:

sleep(delay, result=None, *, loop=None)
Coroutine that completes after a given time (in seconds).

异步编程 asyncio

前言:python 由于GIL(全局锁)的存在,不能发挥多核的优势,其性能一直饱受诟病。然而在 IO 密集型的网络编程里,异步处理比同步处理能提升成百上千倍的效率,弥补了 python 性能方面的短板,如最新的微服务框架 japronto,resquests per second可达百万级。

Python 优势是库(第三方库)极为丰富,运用十分方便。asyncio 是 python3.4 版本引入到标准库,python2.x 没有加这个库,python3.5 又加入了 async / await 特性。

同步 / 异步的概念:

同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行。。。

异步是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果。

asyncio

下面通过举例来对比同步代码和异步代码编写方面的差异,其次看下两者性能上的差距,我们使用sleep(1) 模拟耗时 1 秒的 io 操作。

import time

def run():
    for i in range(5):
        time.sleep(1)
        print('Hello World: %s' % time.time())
if __name__ == '__main__':
    run()

控制台输出:(间隔约是 1 s)

Hello World:1659235809.097074
Hello World:1659235810.1069622
Hello World:1659235811.1164362
Hello World:1659235812.1316037
Hello World:1659235813.14395

asyncio 异步代码

import time
import asyncio

# 定义异步函数
async def func():
    asyncio.sleep(1)
    print('hello World:%s' % time.time())

def run():
    for i in range(5):
        loop.run_until_complete(func())

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

控制台输出:(间隔约是 0 s)

func World:1659237813.2394228
func World:1659237813.2394228
func World:1659237813.2394228
func World:1659237813.2404237
func World:1659237813.2404237

async def 用来定义异步函数,其内部有异步操作。每个线程有一个事件循环,主线程调用asyncio.get_event_loop() 时会创建事件循环,你需要把异步的任务丢给这个循环的 run_until_complete() 方法,事件循环会安排协同程序的执行。

async await Event-loop

函数前 + async keyword 时,实际上是创建了这个函数的 wrapper,当调用这个函数时,实际上会返回一个coroutine object。

首先 正常使用async await

import asyncio

async def main():
    print("Hello World")

print(type(main()))

控制台输出:

<coroutine object main at 0x7feada389ec0>
RuntimeWarning: coroutine 'main' was never awaited
  print(main())
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

coroutine object 与正常 function 不同,如果要等待 coroutine object 的执行结果,需要使用 keyword await 来等待 coroutine 完成返回的结果。我们来试试 await:

import asyncio

async def main():
    print("Hello World")

await main()

控制台输出:

File "F:\唤醒手腕 - 编程日记\唤醒手腕西电 - 2022 - 考研学习\人工智能 - 神经网络\asyncio - 异步编程\test.py", line 6
    await main()
    ^
SyntaxError: 'await' outside function

await 只能在async函数中使用。但是 await 要在 async 函数中使用,而要跑 async 函数,需要 await 等待返回结果。需要使用event-loop

Event-loop

Event-Loop是一个在程序中等待并分发事件或者消息的设计模式。

Python coroutine需要跑在event-loop中

asyncio这个包中提供了一个asyncio.run的函数,可以作为coroutine的入口,asyncio.run会创建一个event-loop,然后将传递给他的coroutine object执行在这个event-loop上,通常asyncio.run这个函数在程序中只会被调用一次,作为coroutine的入口。

import asyncio
async def main():
    print("Hello World")

asyncio.run(main())

asyncio.new_event_loop()来创建一个新的Event-Loop然后通过loop.run_until_complete()来启动一个coroutine

import asyncio


async def main():
    print("Hello World")
    await foo("I am foo")
    print("foo done")


async def foo(text):
    print(text)
    await asyncio.sleep(2)
    print("wake up foo")

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

控制台输出结果:

Hello World
I am foo
wake up foo
foo done

并发 http 请求 aiohttp

如果需要并发http请求怎么办呢,通常是用requests,但requests是同步的库,如果想异步的话需要引入aiohttp。这里引入一个类,from aiohttp import ClientSession,首先要建立一个session对象,然后用session对象去打开网页。session可以进行多项操作,比如 post, get, put, head 等。

基本用法:

async with ClientSession() as session:

async with session.get(url) as response:

aiohttp 异步实现的例子:

import asyncio
from aiohttp import ClientSession


tasks = []
url = "https://www.baidu.com/{}"
async def hello(url):
    async with ClientSession() as session:
        async with session.get(url) as response:
            response = await response.read()
            print(response)

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

首先 async def 关键字定义了这是个异步函数,await 关键字加在需要等待的操作前面,response.read()等待 request 响应,是个耗 IO 操作。然后使用 ClientSession 类发起 http 请求。