由 async & await 引发的协程思考

在与 Python 异步的第一次接触应该是听说大名鼎鼎的异步 web 框架 Tornado ,之后更多的接触是在一些零零散散的资料中看到不少使用 asyncawait 的代码片段。趁着有空系统整理下与之相关的内容。


Python 多线程的”慢“

总所周知,由于 CPythonGIL 的原因,Python 的多线程一直是饱受诟病。在单核情况下的多线程,多个线程只是在短时间内被系统快速调度,各个线程抢占 CPU 时间执行,虽然说和其他语言一样,同一时刻只有一个线程被执行,但是相比其他语言线程调度却多了一个无法避免的获取 GIL 锁的操作 。而在多核情况更加糟糕,由于 GIL 的原因,同一时刻仍然只有一个线程能够被执行。不但多核的优势完全没有发挥出来,而且跨核心的线程调度,CPU 要处理 Tasks 迁移和 Cache 一致性问题,需要付出额外的代价,而其他语言同一时刻理论上有与核心数相同的线程被执行。所以对于计算密集型的任务来说,CPython 的多核多线程不如单核多线程,单核多线程不如单线程。

那么在多核的环境下,Python 除了使用代价较高的多进程以外,就没办法实现并发了吗?另外一个解决方案是:协程。

协程是比线程更小的概念。我们知道进程是系统分配资源的最小单位,进程有独立地址、虚拟内存、文件句柄等资源,代表进程的数据结构( 进程控制块 PCB ,再具体的就是 task_struct 结构体 )由内核负责管理;而线程则是系统调度的最小单位,也是由操作系统内核所管理,但线程的实现在不同的系统中有不同的实现:

  1. 在 Liunx 中,线程是以进程为标准实现的,换句话说线程也是进程,只不过作为线程的进程共享了其逻辑所属的那个进程的数据资源。对于操作系统而言,存在 n 个线程就存在 n 个 task_struct 结构体,部分结构体使用的资源是共享的。因此在 Liunx 中线程又被称为轻量级的进程
  2. 在 Windows ,线程则是真正的更加轻量的执行单元。对于操作系统而言,如果一个进程中存在 n 个线程的话,则是存在一个进程描述符,该进程描述符轮番指向 n 个线程,同时该进程描述符指明了线程间共享的那些资源。

协程的基本概念

在初步搞清楚了进程和线程的基本概念之后,再回头看协程:

协程是比线程更小、更轻量的概念。线程还拥有线程栈,各种局部变量和返回地址等,而线程则更像是一个写好的函数,只不过这个函数内部可中断、可恢复,当恢复到执行状态时函数内部的各种变量和函数中断前保存一致。
那么对比进程切换、线程切换,协程间的切换只需要保存和恢复少量的“上下文”内容,而且协程的切换是在用户空间进行,而不由内核管理,不依赖系统的调度。

在 Python 中要使用协程,需要先对以下几个概念有基本了解:

  • coroutine : 协程对象,是指由 @asyncio.coroutine 装饰器装饰或者由 async 所定义的方法。
  • task : 任务,协程对象是核心,但是对于管理协程的管理者来说还需要知道协程的其他信息(如状态、回调函数、结果)和对协程可进行的操作(如取消、获取当前正在执行的任务、调用栈)。task 对象就是这么一个对协程再封装的对象。
  • future : 任务执行完成后的结果,future 实例对应的类 Future 是 task 实例对应类 Task 的父类,其实也好理解,Task 将任务抽象中的状态信息、绑定回调、执行结果单独拎出来一个类 Future ,自身 Task 主要封装对协程的操作。上层如果要自定义协程的操作也可以通过继承 Future 实现。
  • event loop : 事件循环,就是上面说到的协程管理者。负责管理和执行协程,但是 event loop 通常不直接管理协程对象,而是管理对协程再封装的 task 对象或者 future 对象。

Python 3.4
@asyncio.coroutine & yield from

在 Python 3.4 中导入 asyncio 模块,使用 @asyncio.coroutine 可以定义一个协程,然后通过获取 event loop 来执行协程任务,下面的示例代码的执行结果:先连续打印所有任务的开始语句,过一段时间后再打印所有任务的结束语句。可见任务并不是按照同步的方式顺序执行,而是在一个任务遇到“阻塞”的耗时任务的时候,通过 yield from 交出 CPU 执行权给事件循环,让事件循环执行下一个准备好的任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import asyncio

@asyncio.coroutine
def print_task_no(n):
while True:
print('Task %d began' % n)
yield from asyncio.sleep(1)
print('End of task %d' % n)
yield from asyncio.sleep(1)

if __name__ == "__main__":
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(print_task_no(i)) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

这里要强调的是,当协程通过 yield from 交出 CPU 执行权之后,该协程会被挂起,直到被下次被调用 send 或者 next ,再重新回到上次 yield from 的地方继续执行。

所以说协程是由用户自己(程序)通过 yield from 来主动交出 CPU 执行权的,是属于非抢占式的调度。而多线程本质是抢占式的调度,各个线程通过抢占 CPU 时间片来实现的调度。

  1. 多线程相对于协程的优势:保证调度能够正常的进行。当一个协程被 IO 阻塞而且没有通过 yield from 交出 CPU 执行权的话,整个线程都处于阻塞状态,程序没有并发可言;但是如果是使用多线程的话,即使某个线程被阻塞,其他线程会被 CPU 调度执行,还是能够实现并发。
  2. 协程相对于多线程的优势:保证每次的调度都是有效可进行的。多线程虽然是较为”公平“的抢占式的调度。但是获得 CPU 执行权的线程指不定能够往下执行,如果是仍在阻塞的线程获得了 CPU 执行权,本次调出可以说没有意义。

我还记得刚接触协程的时候我有过以下的定性思考,我试图从 IO 密集型 & 计算密集型,单核 & 多核组合出来的四种情况(假设使用多线程或者协程的进程都完全一样的满载运行)进行分析,当时得到如下结论:

  • IO 密集型、单核:对于单核,首先多进程效率肯定不如单进程。而单进程的协程完胜单进程多线程。
  • IO 密集型、多核:为发挥多核优势,两者都应该使用多进程方式,都建立与核心数的相同的进程数。这时候每个核的情况和 IO 密集型、单核情况一样。所以多进程协程效率高于多进程多线程。
  • 计算密集型、单核:单核,仍然是对单进程分析。这时候由于是计算密集型,协程不会交出 CPU 执行权而一直运行,效果和单线程效果一致。而多线程则需要浪费部分资源在线程切换上面。所以仍然是协程效率高于多线程。
  • 计算密集型、多核:多核,仍然两者都建立与核心数的相同的进程数。单核情况又和计算密集型、单核一致。结论仍然是协程效率高于多线程。

这样看来协程是完胜多线程,那多线程还有存在的意义吗?我开始陷入协程是万能的痛苦。

查询了相关资料才发现,协程并不是什么新概念,协程的提出甚至比抢占式的多线程还要早,操作系统最早就是用协程来模拟多任务并发,但是由于协程是非抢占式的,导致了会有进程一直不交出 CPU 控制权,其他进程无法被有效调度的情况。最终操作系统改用了抢占式的线程调度方式。所以光从操作系统要实现公平调度的需求来说,协程就不能替代多线程。

再者,上面的分析其实是假定协程和线程都满载运行的前提下,再结合协程的切换成本低于线程切换成本这么一个优势得出来的定性结论,当中并没有考虑一些真实的应用场景。假如我需要进行矩阵运算,C++ 在多核情况下使用多线程是能够实现真正的并行计算的,而协程这时候就完全退化成单核单线程模型,只能在一个线程里面利用单核算力一个数一个数的算。

当然了,上面的矩阵运算情景,将矩阵拆分然后使用多进程 + 协程的方式也是可以发挥多核算力。但是当中涉及矩阵拆分与合并,进程通讯等额外问题。而且不是所有矩阵操作都能这样做。所以不考虑这种做法,而且用这种做法来对比单进程多线程模型也不公平。

所以以上对协程与多线程的定性分析是不可取的。回到协程最擅长解决的本质问题:单线程的阻塞问题。是的,使用协程的目的应该是保持线程的流畅运行,提高对单个线程的利用效率。换句话说,协程只有在 IO 密集的情况下适用,而对于计算密集型的来说还是能够充分利用多核算力的多线程模型更加适合。


Python 3.5
async & await

async & await 只不过是 Python 3.5 针对协程装饰器和 yield from 提出的新的语法糖。

于是上面的例子可以改写为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import asyncio

async def print_task_no(n):
while True:
print('Task %d began' % n)
await asyncio.sleep(1)
print('End of task %d' % n)
await asyncio.sleep(1)

if __name__ == "__main__":
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(print_task_no(i)) for i in range(5)]
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

我运行过多次上述代码,始终是按着我加入 tasks 的顺序打印(0,1,2,3,4),但是当我将上述代码修改之后,打印顺序发生了变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import asyncio

async def print_task_no(n):
while True:
print('Task %d began' % n)
await asyncio.sleep(1)
print('End of task %d' % n)
await asyncio.sleep(1)

if __name__ == "__main__":
loop = asyncio.get_event_loop()
# tasks = [asyncio.ensure_future(print_task_no(i)) for i in range(5)] # 传入由 ensure_future 包装得到的 Task 构成的列表
tasks = [print_task_no(i) for i in range(5)] # 直接传入由 coroutine object 构成的列表
loop.run_until_complete(asyncio.wait(tasks))
loop.close()

修改后的代码在我的电脑上运行多次,打印顺序发生了变化。既不是加入列表的顺序,也不某个固定的顺序(某些编辑器,如 PyCharm 可能会遇到每次打印顺序相同问题,可以切换 Run 模式和 Debug 模式来查看打印顺序,可以发现打印顺序并不固定),这相当神奇。

随后查看 asyncio.wait 实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
if futures.isfuture(fs) or coroutines.iscoroutine(fs):
raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
if not fs:
raise ValueError('Set of coroutines/Futures is empty.')
if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
raise ValueError(f'Invalid return_when value: {return_when}')

if loop is None:
loop = events.get_event_loop()

fs = {ensure_future(f, loop=loop) for f in set(fs)}

return await _wait(fs, timeout, return_when, loop)

问题出在第 12 行,将列表传入 asyncio.wait 之后会进行一次 set 操作,顺序自然打乱了,但是奇怪的是,这句代码并没有写在任何的判断条件里,也就是传入 List<Task>List<coroutine object> 应该都会被打乱才对,为什么传入由 Task 构成的列表时,能够实现顺序打印呢?

带着疑惑再查看 asyncio.wait 调用的 ensure_future(f, loop=loop) 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def ensure_future(coro_or_future, *, loop=None):
if coroutines.iscoroutine(coro_or_future):
if loop is None:
loop = events.get_event_loop()
task = loop.create_task(coro_or_future)
if task._source_traceback:
del task._source_traceback[-1]
return task
elif futures.isfuture(coro_or_future):
if loop is not None and loop is not futures._get_loop(coro_or_future):
raise ValueError('loop argument must agree with Future')
return coro_or_future
elif inspect.isawaitable(coro_or_future):
return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)
else:
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
'required')

可以看到如果传入的对象是 coroutine object 的话,会被包装成 Task 再返回;而如果传入的本身就是 Task 的话,则是直接返回。到这里不难猜想,event loop 执行 task 的顺序应该是和 task 创建顺序一致。当我们传入的是 List<Task> 的时候我们是按照顺序创建 task 的,所以打印是按顺序的。但是当我们传入 List<coroutine object> 的时候,是先经过 set 打乱后再被创建成 task 的,因而打印顺序是乱的。

我们可以到 run_until_complete 里面看看:

1
2
3
4
5
6
7
8
9
class AbstractEventLoop:
"""Abstract event loop."""

def run_until_complete(self, future):
"""Run the event loop until a Future is done.

Return the Future's result, or raise its exception.
"""
raise NotImplementedError

可以看到 run_until_complete 是一个需要被子类实现覆盖的抽象方法。

再跳到我们 loop = asyncio.get_event_loop() 获取的那个事件循环子类里看看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def get_event_loop():
"""Return an asyncio event loop.

When called from a coroutine or a callback (e.g. scheduled with call_soon
or similar API), this function will always return the running event loop.

If there is no running event loop set, the function will return
the result of `get_event_loop_policy().get_event_loop()` call.
"""
# NOTE: this function is implemented in C (see _asynciomodule.c)
current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
return get_event_loop_policy().get_event_loop()

def get_event_loop_policy():
"""Get the current event loop policy."""
if _event_loop_policy is None:
_init_event_loop_policy()
return _event_loop_policy


def _init_event_loop_policy():
global _event_loop_policy
with _lock:
if _event_loop_policy is None: # pragma: no branch
from . import DefaultEventLoopPolicy
_event_loop_policy = DefaultEventLoopPolicy()


DefaultEventLoopPolicy = _UnixDefaultEventLoopPolicy


class _UnixDefaultEventLoopPolicy(events.BaseDefaultEventLoopPolicy):
"""UNIX event loop policy with a watcher for child processes."""
_loop_factory = _UnixSelectorEventLoop

def __init__(self):
super().__init__()
self._watcher = None

一步步往下追溯,阅读 asyncio.get_event_loop() 源码可以发现,会优先取得正在运行的 event loop ,如果没有则是通过 get_event_loop_policy() 获取事件循环策略,再往下一步步追溯会发现默认的事件循环策略是 DefaultEventLoopPolicy 变量对应的 _UnixDefaultEventLoopPolicy 类,而 _UnixDefaultEventLoopPolicy 类中的事件循环工厂则是 _UnixSelectorEventLoop

至此,我们知道通过 loop = asyncio.get_event_loop() 获取的事件循环对象是 _UnixSelectorEventLoop 类对应的实例(当然不阅读源码,通过打印 loop.__class__ 也可以得到答案)。

那么我们再看看 _UnixSelectorEventLoop 类对应的 run_until_complete 实现。

阅读源码发现 _UnixSelectorEventLoop 继承自 selector_events.BaseSelectorEventLoopselector_events.BaseSelectorEventLoop 继承自 base_events.BaseEventLoop

所以实际使用的是 base_events.BaseEventLoop 中的 run_until_complete(self, future) 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
class BaseEventLoop(events.AbstractEventLoop):

def run_until_complete(self, future):
"""Run until the Future is done.

If the argument is a coroutine, it is wrapped in a Task.

WARNING: It would be disastrous to call run_until_complete()
with the same coroutine twice -- it would wrap it in two
different Tasks and that can't be good.

Return the Future's result, or raise its exception.
"""
self._check_closed()

new_task = not futures.isfuture(future)
future = tasks.ensure_future(future, loop=self)
if new_task:
# An exception is raised if the future didn't complete, so there
# is no need to log the "destroy pending task" message
future._log_destroy_pending = False

future.add_done_callback(_run_until_complete_cb)
try:
self.run_forever()
except:
if new_task and future.done() and not future.cancelled():
# The coroutine raised a BaseException. Consume the exception
# to not log a warning, the caller doesn't have access to the
# local task.
future.exception()
raise
finally:
future.remove_done_callback(_run_until_complete_cb)
if not future.done():
raise RuntimeError('Event loop stopped before Future completed.')

return future.result()

def run_forever(self):
"""Run until stop() is called."""
self._check_closed()
if self.is_running():
raise RuntimeError('This event loop is already running')
if events._get_running_loop() is not None:
raise RuntimeError(
'Cannot run the event loop while another loop is running')
self._set_coroutine_origin_tracking(self._debug)
self._thread_id = threading.get_ident()

old_agen_hooks = sys.get_asyncgen_hooks()
sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook,
finalizer=self._asyncgen_finalizer_hook)
try:
events._set_running_loop(self)
while True:
self._run_once()
if self._stopping:
break
finally:
self._stopping = False
self._thread_id = None
events._set_running_loop(None)
self._set_coroutine_origin_tracking(False)
sys.set_asyncgen_hooks(*old_agen_hooks)

def _run_once(self):
"""Run one full iteration of the event loop.

This calls all currently ready callbacks, polls for I/O,
schedules the resulting callbacks, and finally schedules
'call_later' callbacks.
"""

# 构建一个最小堆
sched_count = len(self._scheduled)
if (sched_count > _MIN_SCHEDULED_TIMER_HANDLES and
self._timer_cancelled_count / sched_count >
_MIN_CANCELLED_TIMER_HANDLES_FRACTION):
# Remove delayed calls that were cancelled if their number
# is too high
new_scheduled = []
for handle in self._scheduled:
if handle._cancelled:
handle._scheduled = False
else:
new_scheduled.append(handle)

heapq.heapify(new_scheduled)
self._scheduled = new_scheduled
self._timer_cancelled_count = 0
else:
# Remove delayed calls that were cancelled from head of queue.
while self._scheduled and self._scheduled[0]._cancelled:
self._timer_cancelled_count -= 1
handle = heapq.heappop(self._scheduled)
handle._scheduled = False

timeout = None
# 检查就绪队列是否有任务或者是否已经被停止
if self._ready or self._stopping:
timeout = 0
elif self._scheduled:
# Compute the desired timeout.
when = self._scheduled[0]._when
timeout = min(max(0, when - self.time()), MAXIMUM_SELECT_TIMEOUT)

# 获取发生的 IO 事件,使用 IO 多路复用模型进行系统调用
if self._debug and timeout != 0:
t0 = self.time()
event_list = self._selector.select(timeout)
dt = self.time() - t0
if dt >= 1.0:
level = logging.INFO
else:
level = logging.DEBUG
nevent = len(event_list)
if timeout is None:
logger.log(level, 'poll took %.3f ms: %s events',
dt * 1e3, nevent)
elif nevent:
logger.log(level,
'poll %.3f ms took %.3f ms: %s events',
timeout * 1e3, dt * 1e3, nevent)
elif dt >= 1.0:
logger.log(level,
'poll %.3f ms took %.3f ms: timeout',
timeout * 1e3, dt * 1e3)
else:
event_list = self._selector.select(timeout)

# 处理 IO 事件
self._process_events(event_list)

# Handle 'later' callbacks that are ready.
end_time = self.time() + self._clock_resolution
while self._scheduled:
handle = self._scheduled[0]
if handle._when >= end_time:
break
handle = heapq.heappop(self._scheduled)
handle._scheduled = False
self._ready.append(handle)

# This is the only place where callbacks are actually *called*.
# All other places just add them to ready.
# Note: We run all currently scheduled callbacks, but not any
# callbacks scheduled by callbacks run this time around --
# they will be run the next time (after another I/O poll).
# Use an idiom that is thread-safe without using locks.

# 运行就绪队列中的所有任务
ntodo = len(self._ready)
for i in range(ntodo):
handle = self._ready.popleft()
if handle._cancelled:
continue
if self._debug:
try:
self._current_handle = handle
t0 = self.time()
handle._run()
dt = self.time() - t0
if dt >= self.slow_callback_duration:
logger.warning('Executing %s took %.3f seconds',
_format_handle(handle), dt)
finally:
self._current_handle = None
else:
handle._run()
handle = None # Needed to break cycles when an exception occurs.

不难发现执行流程为:

  1. run_until_complete 先检查 event loop 的状态是否关闭。
  2. 对即将执行的 task 对象做类型检查,增加异常处理回调(回调内容大概是当执行某个 task 对象遇到异常则关闭事件循环),然后调用 run_forever
  3. run_forever 而在事件循环中最核心的是 while 循环里面的 _run_once 方法。

asyncio 中的其他细节

进一步细看我们刚才读过的源码,通过 ensure_future 的源码可以发现,能够给 event loop 进行调度的,要么是协程对象本身,要么是经过 ensure_future 包装的 Task 本身,要么是 isawaitable 对象。

关于 inspect.isawaitable 的实现细节与 Awaitable 协议类:

1
2
3
4
5
6
7
8
9
10
11
def isawaitable(object):
"""Return true if object can be passed to an ``await`` expression."""
return (isinstance(object, types.CoroutineType) or
isinstance(object, types.GeneratorType) and
bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE) or
isinstance(object, collections.abc.Awaitable))

@runtime
class Awaitable(Protocol[_T_co]):
@abstractmethod
def __await__(self) -> Generator[Any, None, _T_co]: ...

由此可总结出,能够被 event loop 调度(满足 ensure_future 中的判断条件)的对象包括:

  1. 协程本身(通过 @asyncio.coroutine 装饰器装饰或者通过 async 修饰的方法)

    • 满足 coroutines.iscoroutine(coro_or_future)
  1. 经过 ensure_future 包装的 Task 本身

    • 满足 futures.isfuture(coro_or_future)
  1. 可迭代的协程生成器对象

    • 满足 inspect.isawaitable(coro_or_future) 判断中的 isinstance(object, types.GeneratorType) and bool(object.gi_code.co_flags & CO_ITERABLE_COROUTINE)
  1. 实现了 __await__ 魔术方法的类的实例

    • 满足 inspect.isawaitable(coro_or_future) 判断中的 isinstance(object, collections.abc.Awaitable)

通过源码我们还可以看到,当我们使用 ensure_future 包装得到了 Task 之后,满足的判断却是 futures.isfuture(coro_or_future) ,难道 task 和 future 是继承关系?还真是。

关于 Task 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
class Task(Future[_T], Generic[_T]):
@classmethod
def current_task(cls, loop: AbstractEventLoop = ...) -> Task: ...
@classmethod
def all_tasks(cls, loop: AbstractEventLoop = ...) -> Set[Task]: ...
def __init__(self, coro: Union[Generator[Any, None, _T], Awaitable[_T]], *, loop: AbstractEventLoop = ...) -> None: ...
def __repr__(self) -> str: ...
def get_stack(self, *, limit: int = ...) -> List[FrameType]: ...
def print_stack(self, *, limit: int = ..., file: TextIO = ...) -> None: ...
def cancel(self) -> bool: ...
def _step(self, value: Any = ..., exc: Exception = ...) -> None: ...
def _wakeup(self, future: Future[Any]) -> None: ...

至于 Futrue ,这里指的是我们一直在说的 asyncio.futures.Future ,其实还有另外一个 Futrueconcurrent.futures.Future

它们的作用几乎是一样的。都是用来表示将来执行的任务的结果。 asyncio.futures.Future 相比 concurrent.futures.Futureevent loop 关联性更高,同时直接关联状态,当前loop

至于为什么会同时存在两个 Futrue ,来看看两者的联系和区别:

  1. asyncio.futures.Future 完全兼容 concurrent.futures.Future
  2. asyncio.futures.Future 并不是线程安全的
  3. concurrent.futures.Future 相比,asyncio.futures.Future 某些方法不接收超时参数

总结

其实原意只是想写一篇简单讲解 async & await 使用的博文,然后想到既然要写 async & await 就不得不提一下多进程和多线程,也不得不交代一下 async & await 的前身 @asyncio.coroutine & yield from 。终于写到 async & await 了又回想起自己刚接触协程时候的一些小插曲… 最后彻底走偏成 asyncio 源码历险记。不断的往博文加新内容,但是没有重新梳理文章的大纲,所以文中不少内容的衔接做得不是很好。

同时通过这次对 async & await 的梳理,发现关于多进程/多线程/协程的内容实在是错综复杂,环环相扣。很多我查阅到的资料都还没进行考证,不敢写入文章。只能通过 Python 的小部分源码进行一些皮毛的分析,但是即使如此仍然有很多细节和原理需要进一步学习。

深入理解 ReentrantLock Java Class 加载过程

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×