关于 asyncio 的喃喃自语
大多数东西都是根据之前学习中的印象而来,这篇文章更多的是喃喃自语吧。
协程
在 Python 3.5 之前,协程在我眼中应该就是 yield
这个语法的同义,通过 send
、throw
等方法来作为其交互,用于多进程中以提升效率,然而就 Python 的使用环境来说,其实接触到的机会并不是太多。
在 Python 3.5 之后,给了一种新的,现在看起来还是比较友好的选择
1 | async def hello(): |
在 I/O
密集的地方,填入 await
,在 def
前面加上 async
,不需要去适应至今让我还是十分不适的 yield
/ yield from
EventLoop
最初接触到这个概念应该是 epoll
中,然后是在 Tornado
的源码中,当然,Tornado
也是用了 epoll
。
在 EventLoop
的世界中,它总是与 thread
共存,它只是负责接收事件,余下的由 thread
来解决,保证了并发,在最近阅读的 Kafka
源码中它看起来也是这么干的,不过它可能比起 Tornado
做得更加彻底,Tornado
整个应用只会保持一个 EventLoop
,而 Kafka
为了保证高吞吐高并发,除了 Accept
之外,拥有多个类型注册器,又对应了不同类型的线程池,基本的读和写都不成问题了。
说道这里,才开始说了 asyncio
中的 EventLoop
。如果你只是运行 demo 的话,你会发现获取 event_loop
的方法非常简单 asyncio.get_event_loop()
,但这只是在主线程中,如果你在其他线程中再看
1 | import asyncio |
获得的却是一个 RuntimeError
1 | RuntimeError: There is no current event loop in thread 'LoopThread'. |
需要的是这么做
loop = asyncio.new_evet_loop()
asyncio.set_event_loop(loop)
1 | # 新线程执行的代码: |
其实是有种,多此一举的感觉。
那,event_loop
有什么用呢?
- 它是
asyncio
的起点,是执行所有事件的起点 - 通过
loop.run_forever()
+loop.call_*
实现对事件的调度,最后关闭的时候调用loop.close()
- 通过
loop.add_reader()、loop.remove_reader()、loop.add_writer()、loop.remove_writer()
来注册读写事件,其中有两个参数:文件描述符、callback
,另外还有就是callback
的参数,事件驱动嘛,多用于 socket。 - 通过使用
run_in_executor
来达到将耗时调用委托给线程池/进程池
1 | import asyncio |
Future 与 Future
真的,我第一次知道 Future
是在 Java 中,然后是在 Tornado
中,然后告诉我,Python
中还有两个(完全不兼容) Future
,分别是 asyncio.futures.Future
,concurrent.futures.Future
,而且这两个都在 asyncio
中使用,真是感觉,这屎有毒。
例如,
asyncio.run_coroutine_threadsafe()
将调度一个协程到在另一个线程中运行的事件循环,但它返回一个concurrent.futures.Future
对象,而不是asyncio.futures.Future
对象。 这是有道理的,因为只有concurrent.futures.Future
对象是线程安全的。
你如果在阅读文档的时候你会发现,loop.run_until_complete
里面给出的参数就是 future
,这里的 future
指的是 asyncio.futures.Future
,文档会告诉你 If the argument is a coroutine object, it is wrapped by ensure_future().
(我真的完全不想知道 coroutine object
diff coroutine
),简单来说我们从 ensure_future()
入手知道它大概封装了一个协程,然后读源码的时候发现它给你返回的是一个 task
。
1 | def ensure_future(coro_or_future, *, loop=None): |
这时候你大概能知道 run_until_complete
执行的是一个 task
(当然,你如果去看文档的会知道,它能执行一个 task 列表)。
感想
讲真,Python 3 的 asyncio
在我对比了 epoll
和 Java
的异步机制之后我觉得是将一件本来就不是对初学者很友好的机制再次复杂化,你需要很多新的东西,比如:call
,协程,eventLoop
,task
,future
,executor
等等,以及延展的 yield
,generator
等等。
幸好,社区已经有很多人为我们封装了基础的框架,优秀如 aiohttp
等,希望以后这类理解成本越来越低吧。