啥是异步IO

众所周知,CPU速度太快,磁盘,网络等IO跟不上. 而程序一旦遇到IO的时候,就需要等IO完成才能进行才能进行下一步的操作. 严重拖累了程序速度.

因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发的执行代码.

但是多线程或者多进程虽然解决了并发问题. 但是线程的增加,系统切换线程的开销也会变大. 如果线程太多,CPU的时间就花在了频繁切换线程上.(为啥会有开销,如果不懂的话,请看计算机专业本科教材,操作系统原理)

所以除了多线程和多进程之外,还有一个办法就是异步IO. 也就是传说中的消息订阅机制.

进程发出IO请求后就不管了,然后去干其他的事儿. 等IO返回消息之后再去处理.

如果采用异步IO的话,我们平常的这种顺序执行的代码就不好使了,需要有一个一直在监听事件的消息循环. 一般情况下,我们会使用一个无限循环来进行监听. 是的你没有看错,就是个死循环.

在异步模式下,没有发生频繁的线程切换. 对于IO密集型的场景,异步IO非常合适.

协程

协程,又叫微线程,coroutine.函数在所有语言中都是层级调用,比如A中调用了B,B中又调用了C. 那么C执行完返回,B执行完返回,A执行完.结束.

所以函数的调用都是通过栈来实现的.一个线程执行的就是一个函数.

函数的调用都会有一个入口和一个返回. 所以函数调用的顺序都是明确的. 而协程的调用和函数的调用完全不同.

协程可以在执行的时候中断,然后再在合适的时候返回来接着执行.

协程从执行结果上来看,有点像多线程,但是其优点在于没有发生线程的切换,所以其效率是远高于多线程的. 而且不需要锁...

python中的协程是通过generator来实现的. Python中的yeild不但可以返回一个值,还可以接收调用者发出的参数.

  1. def consumer(): 


  2. r = '' 


  3. while True: 


  4. n = yield r 


  5. if not n: 


  6. return 


  7. print('[CONSUMER] Consuming %s...' % n) 


  8. r = '200 ok' 



  9. def produce(c): 


  10. c.send(None) 


  11. n = 0 


  12. while n < 5: 


  13. n = n + 1 


  14. print('[PRODUCER] Producing %s' % n) 


  15. r = c.send(n) 


  16. print('[PRODUCER] Consumer return %s' % r) 


  17. c.close() 



  18. c = consumer() 


  19. produce(c) 



廖雪峰的代码,应该很好看懂. 不多做解释了.

协程的使用

asyncio是python3.4之后的版本引入的标准库,内置了对异步IO的支持.

asyncio就是一个消息循环模型. 我们从asyncio模块中获取一个eventloop然后把协程丢到这个eventloop中,就可以实现异步IO

  1. import asyncio 



  2. @asyncio.coroutine 


  3. def hello(): 


  4. print('hello world') 


  5. r = yield from asyncio.sleep(2) 


  6. print('hello world again!') 



  7. loop = asyncio.get_event_loop() 


  8. loop.run_until_complete(hello()) 


  9. loop.close() 



  1. import threading 


  2. import asyncio 



  3. @asyncio.coroutine 


  4. def hello(): 


  5. print('hello world! (%s)' % threading.current_thread()) 


  6. yield from asyncio.sleep(2) 


  7. print('hello world again (%s)' % threading.current_thread()) 



  8. loop = asyncio.get_event_loop() 


  9. task = [hello(), hello()] 


  10. loop.run_until_complete(asyncio.wait(task)) 


  11. loop.close() 



这里就可以看出来,两个hello()函数是由同一个线程并发执行的.

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

Run awaitable objects in the aws set concurrently and block until the condition specified by return_when.

If any awaitable in aws is a coroutine, it is automatically scheduled as a Task. Passing coroutines objects to wait() directly is deprecated as it leads to confusing behavior.

这是里关于wait方法的定义. 说实话没搞太懂...

async和await

构造协程并使用asyncio的两种方法. 第一种就是上面说说的. 使用修饰符@asyncio.coroutine 并在协程内使用yield from

另外在python3.5版本后新增了asyncawait关键字. 使用也很简单,用async替代修饰符@asyncio.coroutine 使用await替换yield from

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

Run awaitable objects in the aws set concurrently and block until the condition specified by return_when.Returns two sets of Tasks/Futures: (done, pending).

另外这个方法在python3.8之后被deprecated了.

定义一个协程

  1. import asyncio 


  2. import time 



  3. async def so_some_work(x): 


  4. print('waiting: ',x) 



  5. now = lambda :time.time() 



  6. start = now() 



  7. coroutine = so_some_work(2) 



  8. loop = asyncio.get_event_loop() 


  9. loop.run_until_complete(coroutine) 



  10. print('TIME: ',now() - start) 



这是协程的定义方法,注意协程并不是可以直接执行的函数. 这里通过get_event_loop()方法获取一个一个事件循环,然后通过run_until_complete()方法讲协程注册到消息循环中去,并启动消息循环.

创建一个task

Tasks are used to schedule coroutines concurrently.

When a coroutine is wrapped into a Task with functions like asyncio.create_task() the coroutine is automatically scheduled to run soon

task是Future的子类,保存了协程运行后的状态,用于未来获取协程结果.

协程是不能直接运行的,在使用方法run_until_complete()方法注册协程的时候,协程就自动被包装成了一个task对象.

  1. import asyncio 


  2. import time 



  3. async def do_some_work(x): 


  4. print('Waiting: ', x) 



  5. now = lambda: time.time() 



  6. start = now() 



  7. coroutine = do_some_work(2) 


  8. loop = asyncio.get_event_loop() 


  9. task = loop.create_task(coroutine) 


  10. print(task) 



  11. loop.run_until_complete(task) 


  12. print(task) 


  13. print('TINE: ', now() - start) 



比如这个例子就创建了一个task.


pic-1582726248201.png

从这个结果来看,task在加入时间循环之前是pending的状态. 而运行完之后 是done的状态,而且还有有个result参数.

绑定回调

绑定回调,在task执行完毕之后可以获取执行的结果,也就是协程得返回值. 回调的最后一个参数是future对象,如果回调需要多个参数,也可以通过偏函数导入.

单个参数的callback

  1. import asyncio 


  2. import time 



  3. async def so_some_work(x): 


  4. print('Waitting: ', x) 


  5. return 'Done after {0}s'.format(x) 



  6. def callback(future): 


  7. print('Callback: ', future.result()) 



  8. now = lambda: time.time() 



  9. start = now() 



  10. coroutine = so_some_work(2) 


  11. loop = asyncio.get_event_loop() 


  12. task = asyncio.ensure_future(coroutine) 


  13. task.add_done_callback(callback) 


  14. loop.run_until_complete(task) 



  15. print('TIME: ', now() - start) 



ensure_future()create_task()都可以创建一个task. 不过create_task()是python3.7之后新增的方法.

有多个参数的callback

  1. def callback2(t, future): 


  2. print('Callback: ', t, future.result()) 



  3. -------------------------------------------------- 


  4. task.add_done_callback(functools.partial(callback2, 2)) 



回调让程序逻辑变的更加复杂(就是容易写着写着就懵逼了). 其实在task执行完毕之后,可以直接使用result()方法获取其返回值. 这样就不需要回调了.

  1. import asyncio 


  2. import time 



  3. now = lambda :time.time() 



  4. async def do_some_work(x): 


  5. print('Waitting: ',x) 


  6. return 'Done after {}s'.format(x) 



  7. start = now() 



  8. coroutine = do_some_work(2) 


  9. loop = asyncio.get_event_loop() 


  10. task = asyncio.ensure_future(coroutine) 


  11. loop.run_until_complete(task) 



  12. print('Task ret: {}'.format(task.result())) 


  13. print('TIME: {}'.format(now()-start)) 



await和阻塞

async可以用来定义协程对象, await可以针对耗时的操作进行挂起,就像yield一样可以把协程暂时挂起,让出控制权.

协程遇到await,时间循环会把当前协程暂时挂起,执行别的协程,知道其他的协程也挂起或者执行完毕,再进行下一个协程的执行

针对一些耗时的IO操作一般会是用await的方式挂起它.

  1. import asyncio 


  2. import time 



  3. now = lambda :time.time() 



  4. async def do_some_work(x): 


  5. print('Waitting: ',x) 


  6. await asyncio.sleep(x) 


  7. return 'Done after {}s'.format(x) 



  8. start = now() 



  9. coroutine = do_some_work(2) 


  10. loop = asyncio.get_event_loop() 


  11. task = asyncio.ensure_future(coroutine) 


  12. loop.run_until_complete(task) 



  13. print('Task ret: {}'.format(task.result())) 


  14. print('TIME: {}'.format(now()-start)) 



一个协程看不出啥区别. 多整几个来看看

  1. import asyncio 


  2. import time 



  3. now = lambda :time.time() 



  4. async def do_some_work(x): 


  5. print('Waitting: ',x) 


  6. await asyncio.sleep(x) 


  7. return 'Done after {}s'.format(x) 



  8. start = now() 



  9. coroutine1 = do_some_work(1) 


  10. coroutine2 = do_some_work(2) 


  11. coroutine3 = do_some_work(4) 


  12. loop = asyncio.get_event_loop() 


  13. tasks=[ 


  14. asyncio.ensure_future(coroutine1), 


  15. asyncio.ensure_future(coroutine2), 


  16. asyncio.ensure_future(coroutine3) 






  17. # loop.run_until_complete(asyncio.wait(tasks)) 


  18. loop.run_until_complete(asyncio.wait(tasks)) 



  19. for task in tasks: 


  20. print('Task ret: ',task.result()) 


  21. print('TIME: {}'.format(now()-start)) 



其实我想说到这里,我才真正理解wait方法是干啥的...执行一个task或者协程的集合...

这里还要多说一点就是并行和并发的区别.

并行的意思是同一时刻有多个任务在执行.

并发的意思是有多个任务要同时进行.

按照我的理解,并发就是TDD,并行就是FDD

协程的嵌套(这特么是有病吧...)

不是有病...是为了实现更多的IO操作过程,也就是一个协程中await了另外一个协程

  1. import asyncio 


  2. import time 



  3. now = lambda: time.time() 



  4. async def do_some_work(x): 


  5. print('Waitting: ', x) 


  6. await asyncio.sleep(x) 


  7. return 'Done after {}s'.format(x) 



  8. async def foo(): 


  9. coroutine1 = do_some_work(1) 


  10. coroutine2 = do_some_work(2) 


  11. coroutine3 = do_some_work(4) 



  12. tasks = [ 


  13. asyncio.ensure_future(coroutine1), 


  14. asyncio.ensure_future(coroutine2), 


  15. asyncio.ensure_future(coroutine3) 





  16. dones, pendings = await asyncio.wait(tasks) 



  17. for task in dones: 


  18. print('Task ret: ', task.result()) 



  19. start = now() 


  20. loop = asyncio.get_event_loop() 


  21. loop.run_until_complete(foo()) 



  22. print('TIME: ', now() - start) 



协程的停止

future有几个状态:

  • Pending: task刚创建的时候
  • Running: 时间循环调用执行的时候
  • Done: 执行完成的时候
  • Cancelled: 被cancel()之后
  1. import asyncio 


  2. import time 



  3. now = lambda :time.time() 



  4. async def do_some_work(x): 


  5. print('Waitting: ',x) 


  6. await asyncio.sleep(x) 


  7. print('Done after {}s'.format(x)) 



  8. corutine1 = do_some_work(1) 


  9. corutine2 = do_some_work(2) 


  10. corutine3 = do_some_work(4) 



  11. tasks = [ 


  12. asyncio.ensure_future(corutine1), 


  13. asyncio.ensure_future(corutine2), 


  14. asyncio.ensure_future(corutine3) 






  15. start = now() 



  16. loop = asyncio.get_event_loop() 



  17. try: 


  18. loop.run_until_complete(asyncio.wait(tasks)) 


  19. except KeyboardInterrupt as e: 


  20. print(asyncio.Task.all_tasks()) 


  21. for task in asyncio.Task.all_tasks(): 


  22. print(task.cancel()) 


  23. loop.stop() 


  24. loop.run_forever() 


  25. finally: 


  26. loop.close() 



  27. print('TIME: ', now()-start) 



20200222-实在写不下去了,以后再补吧...


参考资料:

廖雪峰的网站

python黑魔法-简书

python随用随学20200220-异步IO的更多相关文章

  1. 第十一章:Python高级编程-协程和异步IO

    第十一章:Python高级编程-协程和异步IO Python3高级核心技术97讲 笔记 目录 第十一章:Python高级编程-协程和异步IO 11.1 并发.并行.同步.异步.阻塞.非阻塞 11.2 ...

  2. 【Python学习之九】asyncio—异步IO

    asyncio 这是python3.4引入的标准库,直接内置对异步IO的支持.asyncio的编程模型就是一个消息循环.从asyncio模块中直接获取一个EventLoop的引用,然后把需要执行的协程 ...

  3. python中同步、多线程、异步IO、多线程对IO密集型的影响

    目录 1.常见并发类型 2.同步版本 3.多线程 4.异步IO 5.多进程 6.总结 1.常见并发类型 I/ O密集型: 蓝色框表示程序执行工作的时间,红色框表示等待I/O操作完成的时间.此图没有按比 ...

  4. python 学习笔记九 队列,异步IO

    queue (队列) 队列是为线程安全使用的. 1.先入先出 import queue #测试定义类传入队列 class Foo(object): def __init__(self,n): self ...

  5. python网络编程-Select\Poll\Epoll异步IO

    首先列一下,sellect.poll.epoll三者的区别 select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select ...

  6. 【Python之路】特别篇--事件驱动与异步IO

    通常,我们写服务器处理模型的程序时,有以下几种模型: (1)每收到一个请求,创建一个新的进程,来处理该请求: (2)每收到一个请求,创建一个新的线程,来处理该请求: (3)每收到一个请求,放入一个事件 ...

  7. IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO

    IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO ...

  8. 多线程,多进程和异步IO

    1.多线程网络IO请求: #!/usr/bin/python #coding:utf-8 from concurrent.futures import ThreadPoolExecutor impor ...

  9. python学记笔记 2 异步IO

    在IO编程中,我们知道CPU的速度远远快于磁盘,网络IO,在一个线程中,CPU执行速度的代码非常快,然而遇到IO操作就需要阻塞 需要等待IO操作完成才能继续下一步的动作.这种情况叫做同步IO 在IO操 ...

随机推荐

  1. 5.JavaSE之数据类型详解

    数据类型: 强类型语言: 要求变量的使用严格要求符合规定,写错了就不行,所有变量都必须先定义后才能使用,否则是不能使用的. 比如Java.C++都是强类型语言,也就是说,一旦定义了一个变量,只定义了某 ...

  2. B-Tree 和 B+Tree 结构及应用,InnoDB 引擎, MyISAM 引擎

    1.什么是B-Tree 和 B+Tree,他们是做什么用的? B-Tree是为了磁盘或其它存储设备而设计的一种多叉平衡查找树,B-Tree 和 B+Tree 广泛应用于文件存储系统以及数据库系统中. ...

  3. WeihanLi.Npoi 根据模板导出Excel

    WeihanLi.Npoi 根据模板导出Excel Intro 原来的导出方式比较适用于比较简单的导出,每一条数据在一行,数据列虽然自定义程度比较高,如果要一条数据对应多行就做不到了,于是就想支持根据 ...

  4. python类型-序列-字符串

    python中单引号和双引号的含义是一样的.字符串是一种直接量或者说是一种标量,是不可变类型,字符串是由独立的字符组成的,并且这些字符可以通过切片操作顺序的访问. python实际有三类字符串:通常意 ...

  5. 20191211 HNOI2017 模拟赛 问题A

    题目: 分析: 好难好难... 下来听神仙讲.. 每一个长度为n-2的prufer序列一一对应一棵大小为n的树... 每个点在序列中的出现次数为该点的度数减一 哦??? ... 哦... prufer ...

  6. 投入OJ的怀抱~~~~~~~~~~

    OpenJudge C20182024 信箱(1) 账号 修改设定 退出小组 管理员 frank 林舒 Dzx someone 李文新 公告 11-05 程序设计与算法(大学先修课) 成员(61910 ...

  7. 面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他

    初识 MyBatis MyBatis 是第一个支持自定义 SQL.存储过程和高级映射的类持久框架.MyBatis 消除了大部分 JDBC 的样板代码.手动设置参数以及检索结果.MyBatis 能够支持 ...

  8. 优雅写Java之一(常见编程技巧)

    一.字符串相关 推荐使用Apache Commons Lang3库 创建Empty字符串:return StringUtils.EMPTY; 或者 return ""; 创建重复的 ...

  9. STM32学习笔记:基础例子

    本例子代码参考了STM32库开发实战指南中的代码,由于使用的板子是尚学STM32F103ZET6,为了配合板上已有资源,也参考了其配套代码.为了便于书写文本,我尽量将代码都写到了一个文件中,这种方式是 ...

  10. IntelliJ IDEA 2020 的Debug功能也太好用了,真香!

    写在前边 作为一个有点强迫症的程序员来说,所有的应用软件.开发工具都必须要升级到最高版本,否则就会很难受到坐立不安.日思夜想.茶饭不思.至于什么时候得的这种病我也记不清了,哈哈哈 IntelliJ I ...