ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

Python asyncio 异步IO的理解

2021-07-30 19:31:35  阅读:340  来源: 互联网

标签:__ task 协程 name Python IO CPU loop asyncio


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()  # 关闭事件循环

运行

 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()  # 关闭事件循环

运行

 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()  # 关闭事件循环

运行

 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()  # 关闭事件循环

运行

 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()  # 关闭事件循环

运行

 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))

运行

可以看出,当运行到睡眠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))

运行

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

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

标签:__,task,协程,name,Python,IO,CPU,loop,asyncio
来源: https://www.cnblogs.com/rainbow-tan/p/15081118.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有