python随用随学20200220-异步IO

Posted 吾码2016

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python随用随学20200220-异步IO相关的知识,希望对你有一定的参考价值。

啥是异步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.  
  10. def produce(c): 
  11. c.send(None) 
  12. n = 0 
  13. while n < 5: 
  14. n = n + 1 
  15. print(\'[PRODUCER] Producing %s\' % n) 
  16. r = c.send(n) 
  17. print(\'[PRODUCER] Consumer return %s\' % r) 
  18. c.close() 
  19.  
  20. c = consumer() 
  21. produce(c) 
  22.  

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

协程的使用

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

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

  1. import asyncio 
  2.  
  3. @asyncio.coroutine 
  4. def hello(): 
  5. print(\'hello world\') 
  6. r = yield from asyncio.sleep(2) 
  7. print(\'hello world again!\') 
  8.  
  9. loop = asyncio.get_event_loop() 
  10. loop.run_until_complete(hello()) 
  11. loop.close() 
  12.  
  1. import threading 
  2. import asyncio 
  3.  
  4. @asyncio.coroutine 
  5. def hello(): 
  6. print(\'hello world! (%s)\' % threading.current_thread()) 
  7. yield from asyncio.sleep(2) 
  8. print(\'hello world again (%s)\' % threading.current_thread()) 
  9.  
  10. loop = asyncio.get_event_loop() 
  11. task = [hello(), hello()] 
  12. loop.run_until_complete(asyncio.wait(task)) 
  13. loop.close() 
  14.  

这里就可以看出来,两个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.  
  4. async def so_some_work(x): 
  5. print(\'waiting: \',x) 
  6.  
  7. now = lambda :time.time() 
  8.  
  9. start = now() 
  10.  
  11. coroutine = so_some_work(2) 
  12.  
  13. loop = asyncio.get_event_loop() 
  14. loop.run_until_complete(coroutine) 
  15.  
  16. print(\'TIME: \',now() - start) 
  17.  

这是协程的定义方法,注意协程并不是可以直接执行的函数. 这里通过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对象.