Python asyncio 异步IO的理解

1、理解概念

asyncio 是用来编写并发代码的库,使用 async/await 语法。

(1)何为并发:

并发就是在一个时间段内,同时做多个事情。

比如在单CPU的机器中(只有一个CPU的机器),我们可以一边听歌,一边斗地主,一边聊QQ。

在我们看来,我们是同时在做这三件事,可是在单CPU中,这并不是同时进行的。

单CPU把自己分成了一段一段的时间片,每个时间片用来做一个事情。

如果该时间片用光了,但是事件还没有做完,那就先不做了。CPU先去做其他的,做完其他的事情,再利用新的时间片,来接着做这个事情。

因为时间片之间的切换速度是很快的,用户根本体验不出来,所以就感觉是同时在做这些事情。

以上就可以理解为并发。

(2)何为并行

基本上提到了并发,总有人要问什么是并行,以及并行与并发的区别。

并行就是同一时间点,同时做多个事情。听起来和并发差不多,但是还是有区别的,看完下面的解释就容易理解了。

并行在单CPU中是不可能存在的,因为并行是真真实实存在的。而并发是宏观上存在的,

比如在多CPU的机器中(有多个CPU的机器,与单CPU对立),我们可以一边听歌,一边斗地主,一边聊QQ。

在我们看来,我们是同时在做这三件事,在多CPU中,其实也是同时在做这三个事情

一个CPU负责听歌的进程,一个CPU负责斗地主的进程,一个CPU负责QQ的进程。

在物理时间上,CPU真的就是在同时做这三个事情,只是每个事件用的CPU不同而已。

以上可以理解为并行。

备注:我这里所说的多CPU,一个CPU负责一个进程,其实也可以理解为一个CPU有多个核,一个核负责一个进程,这样也是说的通的。

因为我对多CPU以及多核CPU还没有理解清楚,所以上面的举例就用了多CPU。本文主要是记录自己的理解,所以难免有不懂的地方。请包涵。

(3)并发与并行的共同点与区别

共同点:

我们可以理解并发与并行都是同一个时间,做多个事情,执行多个程序。

不同点:

并发是在宏观上存在的,是在逻辑上存在的,理解了(1),应该就能理解这句话了。这也是为什么用单CPU举例的意义所在。

并行是在物理上存在的,是真实存在的。理解了(2),应该就能理解这句话了。

单核CPU是不可能存在并行的。

单CPU中进程只能是并发,多CPU计算机中进程可以并行。

单CPU单核中线程只能并发,单CPU多核中线程可以并行。

(4)并发与并行学习链接

https://cloud.tencent.com/developer/article/1424249 图形并茂的,助于理解!

https://laike9m.com/blog/huan-zai-yi-huo-bing-fa-he-bing-xing,61/ 义正言辞的!

https://sunweiguo.github.io/2019/11/17/miscellany/20%E5%85%B3%E4%BA%8E%E5%A4%9ACPU%E5%92%8C%E5%A4%9A%E6%A0%B8CPU%E7%9A%84%E5%8C%BA%E5%88%AB/  CPU知识点!

(5)协程理解

一般实现并发的方式就是使用多进程,多线程。这里加入一个协程概念,协程也可以实现并发。

asyncio就是通过协程,实现的并发。与本文第一句相互呼应,嘿嘿!

我们编写程序,正常执行是串行的,即从上而下依次执行。

比如定义一个函数,执行函数就是从函数的第一行,依次执行第二行,再执行第三行......最后执行最后一行,然后结束函数的执行。

协程就是执行一个函数,到某一行,然后转去执行其他函数,执行其他函数到某一行,又去执行了其他函数,最后在合适的时间点,又转回来执行最初的函数

在合适的时间点,指的是其他函数运行结束,或者其他函数主动释放CPU。

比如某个函数中有sleep操作,这个时间,等待睡眠是浪费CPU的,这个时候可以跳出该函数,转去执行其他函数,当其他函数执行完,再接着执行这个sleep的后一句

以上就是个人理解协程,比较空洞,结合下面的代码,就能容易理解一点了。

2、使用async关键字定义协程

def function(name):
    print('这是正常的函数,name:{}'.format(name))
async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))
if __name__ == '__main__':
    function('hello')  # 正常执行
    coroutines_function('hello')  # 报错 coroutine 'coroutines_function' was never awaited

3、通过asyncio运行协程(开始了asycnio的使用)

协程对象不能直接运行,需要添加到事件循环中,在合适的时间点,事件循环自动运行

合适的时间点指的是CPU分配时间片给他,其他程序不占用CPU

 import asyncio  # 导入异步io库


async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))


if __name__ == '__main__':
    obj = coroutines_function('墨玉麒麟')  # 返回协程对象
    print(type(obj))  # <class 'coroutine'>
    loop = asyncio.get_event_loop()  # 获取事件循环
    loop.run_until_complete(obj)  # 注册到事件循环中
    loop.close()  # 关闭事件循环

运行

Python asyncio 异步IO的理解

 4、创建task对象

上面3中的示例代码 loop.run_until_complete(obj) 

传入的参数是一个协程对象,传入后协程对象会被包装成task对象,这样运行的就是task对象,而不是协程对象

可以这么写,创建一个task对象,然后传给run_until_complete函数

import asyncio  # 导入异步io库


async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))


if __name__ == '__main__':
    obj = coroutines_function('墨玉麒麟')  # 返回协程对象
    print(type(obj))  # <class 'coroutine'>
    loop = asyncio.get_event_loop()  # 获取事件循环
    task = loop.create_task(obj)  # 创建task
    print(type(task))  # <class '_asyncio.Task'>
    loop.run_until_complete(task)  # 注册到事件循环中
    loop.close()  # 关闭事件循环

运行

Python asyncio 异步IO的理解

 5、task对象的状态

task对象被创建出来时,不会被执行,这时候状态是pending

之后被添加到事件循环中,等待执行

执行结束后状态为finished

import asyncio  # 导入异步io库


async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))


if __name__ == '__main__':
    obj = coroutines_function('墨玉麒麟')  # 返回协程对象
    print(type(obj))  # <class 'coroutine'>
    loop = asyncio.get_event_loop()  # 获取事件循环
    task = loop.create_task(obj)  # 创建task
    print(type(task))  # <class '_asyncio.Task'>
    print(task)  # <Task pending coro=<coroutines_function()
    loop.run_until_complete(task)  # 注册到事件循环中
    print(task)  # <Task finished coro=<coroutines_function() done
    loop.close()  # 关闭事件循环

运行

Python asyncio 异步IO的理解

 6、回调函数

可以为task对象添加一个回调函数,执行完task后,自动执行绑定的回调函数

import asyncio  # 导入异步io库


async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))


def callback(function):
    print('协程函数本身:{}'.format(function))
    print('回调函数完成 ---> coroutines_function')


if __name__ == '__main__':
    obj = coroutines_function('墨玉麒麟')  # 返回协程对象
    loop = asyncio.get_event_loop()  # 获取事件循环
    task = loop.create_task(obj)  # 创建task
    task.add_done_callback(callback)
    loop.run_until_complete(task)  # 注册到事件循环中
    loop.close()  # 关闭事件循环

运行

Python asyncio 异步IO的理解

 7、获取协程函数的返回值

只有当协程函数执行完毕,状态为finished时,才可以获取返回值,提前获取报错

import asyncio  # 导入异步io库


async def coroutines_function(name):
    print('这是协程函数,name:{}'.format(name))
    return 'coroutines_function-->{}'.format(name)


def callback(function):
    print('协程函数本身:{}'.format(function))
    print('回调函数完成 ---> coroutines_function')


if __name__ == '__main__':
    obj = coroutines_function('墨玉麒麟')  # 返回协程对象
    loop = asyncio.get_event_loop()  # 获取事件循环
    task = loop.create_task(obj)  # 创建task
    # print(task.result())  # 报错 Result is not set.
    task.add_done_callback(callback)
    loop.run_until_complete(task)  # 注册到事件循环中
    print('task result:{}'.format(task.result()))  # coroutines_function-->墨玉麒麟
    loop.close()  # 关闭事件循环

运行

Python asyncio 异步IO的理解

 8、await让出CPU控制器

以上的例子都不能演示并发的情景,因为以上的例子没有用到await关键字

我们可以通过使用await关键字,来让出CPU的控制器,去执行其他函数的语句,从而实现并发机制

当事件循环看到await关键字时,事件循环会挂起该协程,从而去执行其他协程。其他协程挂起时,或者其他协程执行完毕,事件循环再继续执行最初的协程

(1)举个睡眠的例子

import asyncio  # 导入异步io库
import time


async def sleep_second(second):
    print('将要睡眠{}秒'.format(second))
    await asyncio.sleep(second)
    print('睡眠{}秒完成,醒过来了'.format(second))
    return '休息了{}秒'.format(second)


if __name__ == '__main__':
    start = time.time()
    obj1 = sleep_second(5)  # 返回协程对象
    obj3 = sleep_second(3)  # 返回协程对象
    obj5 = sleep_second(1)  # 返回协程对象
    loop = asyncio.get_event_loop()  # 获取事件循环
    task1 = loop.create_task(obj1)  # 创建task
    task3 = loop.create_task(obj3)  # 创建task
    task5 = loop.create_task(obj5)  # 创建task
    loop.run_until_complete(task1)  # 注册到事件循环中
    loop.run_until_complete(task3)  # 注册到事件循环中
    loop.run_until_complete(task5)  # 注册到事件循环中
    loop.close()  # 关闭事件循环
    end = time.time()
    print('耗时:{}'.format(end - start))

运行

Python asyncio 异步IO的理解

可以看出,当运行到睡眠5秒的时候,程序并没有等待,而是去运行睡眠3秒,再去运行睡眠1秒,最后1秒先执行完毕,先返回,再是睡眠3秒返回,再是睡眠5秒返回

用时是5秒,而不是5 + 3 + 1 = 9 秒

备注 await后面需要跟协程函数,而不能是普通函数,这里不能用time.sleep()代替asyncio.sleep(),用了会报错,可以自己换了尝试一下看看

(2)演示协程嵌套

import asyncio  # 导入异步io库
import time


async def fun1(name):
    print('fun1 name begin:{}'.format(name))
    print('fun1 name end:{}'.format(name))


async def fun2(name):
    print('fun2 name begin:{}'.format(name))
    await fun1('墨玉麒麟')
    print('fun2 name end:{}'.format(name))


async def fun3(name):
    print('fun3 name begin:{}'.format(name))
    await fun2('张龙赵虎')
    print('fun3 name end:{}'.format(name))


if __name__ == '__main__':
    start = time.time()
    obj3 = fun3('神龙吸水')  # 返回协程对象
    loop = asyncio.get_event_loop()  # 获取事件循环
    task3 = loop.create_task(obj3)  # 创建task
    loop.run_until_complete(task3)  # 注册到事件循环中
    loop.close()  # 关闭事件循环
    end = time.time()
    print('耗时:{}'.format(end - start))

运行

Python asyncio 异步IO的理解

 备注:实际项目中,一般asyncio.sleep()都是用其他协程的库来代替,比如aiohttp、 aiomysq等

学习链接: https://www.jianshu.com/p/b5e347b3a17c

上一篇:python async异步函数的单元(单独)测试


下一篇:asyncio 简单使用