理解 Python 异步编程原理与 asyncio 简单实现
在开始说明异步编程之前,首先先了解几个相关的核心概念。这些概念是理解现代高性能网络编程的基础。
基本概念辨析
阻塞 (Blocking)
程序未得到所需计算资源时被挂起的状态。程序在等待某个操作完成期间,自身无法继续干别的事情,则称该程序在该操作上是阻塞的。
常见的阻塞形式有:网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。
阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。(如果是多核 CPU 则正在执行上下文切换操作的核不可被利用。)
简单的理解的话,阻塞就是 A 调用 B,A 会被挂起,一直等待 B 的结果,什么都不能干。
非阻塞 (Non-blocking)
程序在等待某操作过程中,自身不被阻塞,可以继续运行干别的事情,则称该程序在该操作上是非阻塞的。
非阻塞并不是在任何程序级别、任何情况下都可以存在的。 仅当程序封装的级别可以囊括独立的子程序单元时,它才可能存在非阻塞状态。
非阻塞的存在是因为阻塞存在,正因为某个操作阻塞导致的耗时与效率低下,我们才要把它变成非阻塞的。
简单理解的话,非阻塞就是 A 调用 B,A 自己不用被挂起来等待 B 的结果,A 可以去干其他的事情。
同步 (Synchronous)
不同程序单元为了完成某个任务,在执行过程中需靠某种通信方式以协调一致,称这些程序单元是同步执行的。
例如购物系统中更新商品库存,需要用'行锁'作为通信信号,让不同的更新请求强制排队顺序执行,那更新库存的操作是同步的。
简言之,同步意味着有序。
简单理解的话,同步就是A 调用 B,此时只有等 B 有了结果才返回。
异步 (Asynchronous)
为完成某个任务,不同程序单元之间过程中无需通信协调,也能完成任务的方式。 不相关的程序单元之间可以是异步的。
例如,爬虫下载网页。调度程序调用下载程序后,即可调度其他任务,而无需与该下载任务保持通信以协调行为。不同网页的下载、保存等操作都是无关的,也无需相互通知协调。这些异步操作的完成时刻并不确定。
简言之,异步意味着无序。
简单理解的话,异步就是A 调用 B,B 立即返回,无需等待。等 B 处理完之后再告诉 A 结果。
并发 (Concurrency) vs 并行 (Parallelism)
并发
并发描述的是程序的组织结构。指程序要被设计成多个可独立执行的子任务。 以利用有限的计算机资源使多个任务可以被实时或近实时执行为目的。
并行
并行描述的是程序的执行状态。指多个任务同时被执行。 以利用富余计算资源(多核 CPU)加速完成多个任务为目的。
并发提供了一种程序组织结构方式,让问题的解决方案可以并行执行,但并行执行不是必须的。
总的来说,并行是为了利用多核加速多任务的完成;并发是为了让独立的子任务能够尽快完成;非阻塞是为了提高程序的整体运行效率,而异步是组织非阻塞任务的方式。
从同步到异步 I/O 的演进
以一个爬虫为例,下载 10 篇网页,用几个例子来展示从同步->异步的演进过程。
同步阻塞方式
以同步阻塞方式来写这个程序也是最容易想到的方式,即依次下载好 10 篇网页。
import socket
def blocking_way():
sock = socket.socket()
# 阻塞
sock.connect(('example.com', 80))
request = 'GET / HTTP/1.0\r\nHost: example.com\r\n\r\n'
sock.send(request.encode())
response =
chunk = sock.recv()
chunk:
response += chunk
chunk = sock.recv()
response
():
res = []
i ():
res.append(blocking_way())
(res)


