一文学会Python中的异步编程

异步处理是一种编程范式,允许任务或操作在不阻塞主程序执行的情况下并发执行。

在传统的同步处理中,任务按顺序一个接一个地执行,程序在移动到下一个任务之前等待每个任务完成。

这种方法可能会导致效率低下,特别是在处理涉及等待 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 引入了 asyncawait 关键字,用于定义协程并管理异步代码的执行流程。协程是可以暂停和恢复的函数,允许在此期间运行其他任务。事件循环作为中央调度程序,协调这些协程的执行并有效地管理 I/O 操作。

主要特性

  • 协程: 使用 async 关键字定义的函数。它们可以在执行过程中暂停和恢复,允许其他协程同时运行。
  • 事件循环: 事件循环为协程提供了执行环境。它管理多个协程之间的调度和切换,确保高效和非阻塞的执行。
  • 任务: 任务是在协程之上的更高级别抽象。它们代表需要异步执行的工作单元,并可以由事件循环进行调度和管理。
  • Future: Future 是表示可能尚未完成的协程结果的对象。它们允许你跟踪和等待异步操作的完成。
  • 同步原语: asyncio 提供了各种同步原语,如锁、信号量和队列,有助于管理共享资源和协调并发操作。

然而,asyncio 采用单线程、单进程模型,依赖于协作式多任务处理。通常说来,尽管 asyncio 在单个线程中的单个进程中运行,但它会创造出一种并发的假象。协程,asyncio 的关键元素,可以并发地部署,但它们的性质并不是固有的并发性。

强调一下,asyncio 是一种并发编程方式,但它不等同于并行。它的方法论更接近于线程而不是多进程,但它又不同于两者,在并发技术的众多方法中独树一帜。

async 和 await

asyncawait 语法是 Python 的 asyncio 模块的关键组件,这是一个使用 async/await 语法编写并发代码的库。

async

async 是用于声明函数为“异步函数”的关键字。这样的函数也称为“协程”。你可以通过在 def 前加上 async 来定义一个协程。例如,async def my_function():

当调用一个 async 函数时,它不会按传统方式执行。相反,它返回一个“可等待”对象,即协程对象。这个对象需要被等待或在事件循环中运行以获取结果。事件循环是异步代码执行的地方。它是每个 asyncio 应用程序的核心,管理和调度异步任务的执行。

await

await 用于暂停协程,直到等待的任务完成。它只能在 async 函数内部使用。await 关键字后跟着一个表达式,该表达式是一个“可等待”对象,比如一个协程、一个 Future 或者一个 I/O 密集型函数。

当执行 await 表达式时,包含它的协程将暂停,直到等待的对象完成。在此暂停期间,事件循环将继续运行其他

任务。

为什么使用 asyncawait

  • 非阻塞: 异步代码允许非阻塞的 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())
  • 25
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值