异步处理是一种编程范式,允许任务或操作在不阻塞主程序执行的情况下并发执行。
在传统的同步处理中,任务按顺序一个接一个地执行,程序在移动到下一个任务之前等待每个任务完成。
这种方法可能会导致效率低下,特别是在处理涉及等待 I/O 的操作时,如文件操作或网络请求,程序会在相当长的时间内处于空闲状态。
举个例子,如果你运行以下代码:
import time
# 同步执行的函数
def task(name):
print(f"任务 {name} 开始")
time.sleep(2) # 模拟延迟 2 秒
print(f"任务 {name} 完成")
# 主程序
print("主程序开始")
# 顺序执行任务
task("A")
task("B")
task("C")
print("主程序完成")
你会看到以下输出:
主程序开始
任务 A 开始
任务 A 完成
任务 B 开始
任务 B 完成
任务 C 开始
任务 C 完成
主程序完成
相比之下,异步处理使得任务能够独立并发执行。它允许程序启动一个任务并继续执行其他操作,而不必等待任务完成。当任务完成后,通过通知或事件触发,程序可以处理其结果或继续执行。
例如:
import asyncio
# 表示一个任务的异步协程
async def task(name):
print(f"任务 {name} 开始")
await asyncio.sleep(2) # 模拟延迟 2 秒
print(f"任务 {name} 完成")
# 主程序
async def main():
print("主程序开始")
# 创建要并发执行的任务列表
tasks = [
asyncio.create_task(task("A")),
asyncio.create_task(task("B")),
asyncio.create_task(task("C"))
]
# 等待所有任务完成
await asyncio.gather(*tasks)
print("主程序完成")
# 运行主程序
asyncio.run(main())
输出:
主程序开始
任务 A 开始
任务 B 开始
任务 C 开始
任务 A 完成
任务 B 完成
任务 C 完成
主程序完成
可以看到,所有任务都并发启动,并且它们的完成顺序可能会有所不同。主程序在不等待任务完成的情况下继续执行,从而实现了异步处理。
通常使用异步编程技术(如协程、事件驱动编程或基于回调的编程模型)来实现异步处理。这些技术允许程序在不阻塞的情况下在多个任务之间切换,最大限度地利用系统资源。
异步处理的好处包括提高性能、响应性和可伸缩性。通过避免不必要的等待并最大限度地利用资源,应用程序可以高效处理大量并发操作。
并行 vs 并发 vs 线程
并行
并行是指通过使用多个计算单元同时执行多个任务。这通常在具有多核处理器的系统中看到,其中每个任务在单独的 CPU/处理器上运行。这主要是硬件特性。在真正的并行中,多个任务同时物理执行,就像多个工人同时涂刷庞大墙壁的不同部分一样。
from multiprocessing import Process
def worker(num):
print(f'工人: {num}')
if __name__ == '__main__':
processes = [Process(target=worker, args=(i,)) for i in range(5)]
for process in processes:
process.start()
for process in processes:
process.join()
输出:
工人: 1
工人: 0
工人: 2
工人: 3
工人: 4
并发
并发是指同时处理多个任务,但不一定是同时执行它们。多个任务可以在彼此不等待的情况下取得进展,但它们实际上可能不会在完全相同的时刻运行。
这种情况可以发生在单核(时间切片)和多核处理器上。它更多地涉及任务的结构和完成,而不是执行。一个简单的例子可以帮助理解并发,比如一个单独的厨师在厨房里工作,他同时烹饪多道菜,通过快速切换任务给人一种他在同时执行任务的印象。
import asyncio
async def worker(num):
print(f'开始工作: {num}')
await asyncio.sleep(1)
print(f'完成工作: {num}')
async def main():
# 创建要并发执行的任务列表
tasks = [
asyncio.create_task(worker(1)),
asyncio.create_task(worker(2)),
asyncio.create_task(worker(3))
]
# 等待所有任务完成
await asyncio.gather(*tasks)
asyncio.run(main())
输出:
开始工作: 1
开始工作: 2
开始工作: 3
完成工作: 1
完成工作: 2
完成工作: 3
线程
线程是一种编程概念和实现并发的技术。线程是由操作系统的调度程序独立管理的最小编程指令序列。
一个程序可以包含多个线程,每个线程可以并发运行,各自处理特定的任务。通过管理多个线程,单个进程可以以并发的方式执行任务,从而使整体执行更快更高效。
如果系统具有多核处理器,这些单独的线程也可以并行运行。线程的一个典型应用是 Web 服务器处理来自不同用户的多个请求,其中每个请求由单独的线程处理。
关于线程需要认识的一个关键点是,在处理 IO 密集型任务时,它的效率较高。与 CPU 密集型任务不同,后者从开始到结束都严重依赖于 CPU 的处理能力,而 IO 密集型任务则在等待输入/输出操作结束时有很长的空闲时间。
import threading
def worker(num):
print(f'工作者: {num}')
if __name__ == '__main__':
threads = [threading.Thread(target=worker, args=(i,)) for i in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
输出:
工作者: 0
工作者: 1
工作者: 2
工作者: 4
工作者: 3
总的来说,在并发系统中,任务在重叠的时间段内启动、运行和完成,而在并行系统中,任务同时运行 — 这是关键区别。线程仅仅是一种实现并发和可能并行的技术。并发包括多处理,适用于 CPU 密集型任务,以及线程,专门用于 IO 密集型任务。多处理可以被视为一种并行,而并行本身则是并发的一种特殊形式(子集)。
Python 的 Asyncio 模块
Python 的 asyncio
模块是一个强大的库,提供了编写异步代码的支持。它建立在协程、事件循环和异步 I/O 的概念之上,以实现高效和并发的编程。
使用 asyncio
进行异步编程允许你编写单线程并发代码,其中多个任务可以同时执行,同时避免阻塞操作。这种方法特别适用于处理 I/O 密集型任务,比如网络请求或文件操作,因为这些任务往往有很长的等待时间。
asyncio
引入了 async
和 await
关键字,用于定义协程并管理异步代码的执行流程。协程是可以暂停和恢复的函数,允许在此期间运行其他任务。事件循环作为中央调度程序,协调这些协程的执行并有效地管理 I/O 操作。
主要特性
- 协程: 使用 async 关键字定义的函数。它们可以在执行过程中暂停和恢复,允许其他协程同时运行。
- 事件循环: 事件循环为协程提供了执行环境。它管理多个协程之间的调度和切换,确保高效和非阻塞的执行。
- 任务: 任务是在协程之上的更高级别抽象。它们代表需要异步执行的工作单元,并可以由事件循环进行调度和管理。
- Future: Future 是表示可能尚未完成的协程结果的对象。它们允许你跟踪和等待异步操作的完成。
- 同步原语:
asyncio
提供了各种同步原语,如锁、信号量和队列,有助于管理共享资源和协调并发操作。
然而,asyncio
采用单线程、单进程模型,依赖于协作式多任务处理。通常说来,尽管 asyncio
在单个线程中的单个进程中运行,但它会创造出一种并发的假象。协程,asyncio
的关键元素,可以并发地部署,但它们的性质并不是固有的并发性。
强调一下,asyncio
是一种并发编程方式,但它不等同于并行。它的方法论更接近于线程而不是多进程,但它又不同于两者,在并发技术的众多方法中独树一帜。
async 和 await
async
和 await
语法是 Python 的 asyncio
模块的关键组件,这是一个使用 async/await 语法编写并发代码的库。
async
async
是用于声明函数为“异步函数”的关键字。这样的函数也称为“协程”。你可以通过在 def
前加上 async
来定义一个协程。例如,async def my_function():
。
当调用一个 async
函数时,它不会按传统方式执行。相反,它返回一个“可等待”对象,即协程对象。这个对象需要被等待或在事件循环中运行以获取结果。事件循环是异步代码执行的地方。它是每个 asyncio 应用程序的核心,管理和调度异步任务的执行。
await
await
用于暂停协程,直到等待的任务完成。它只能在 async
函数内部使用。await
关键字后跟着一个表达式,该表达式是一个“可等待”对象,比如一个协程、一个 Future 或者一个 I/O 密集型函数。
当执行 await
表达式时,包含它的协程将暂停,直到等待的对象完成。在此暂停期间,事件循环将继续运行其他
任务。
为什么使用 async
和 await
- 非阻塞: 异步代码允许非阻塞的 I/O 操作。当等待一个可等待对象时,其他代码可以运行。
- 并发: 它使得以更高效和易于维护的方式编写并发代码成为可能。
- 性能: 在 I/O 密集型应用程序中,使用 async/await 可以带来显著的性能改进。
- 可读性: 与 Python 中旧的异步编程技术(如回调)相比,async/await 语法更加清晰和直观。
示例:
import asyncio
async def my_async_function():
await asyncio.sleep(1)
return "Hello, Async World!"
async def main():
result = await my_async_function()
print(result)
# 运行主协程
asyncio.run(main())