Python面试官:你来解释一下协程的实现原理

本篇文章主要内容:

  • 协程的基本概念和Python协程语法
  • Python 协程常用的 API
  • 协程适用场景

1 基本概念

异步编程允许程序在等待 I/O 操作完成时继续执行其它任务,而不是被阻塞。异步编程是Python处理并发操作的强大方式,特别适合I/O密集型任务。Python的异步编程主要基于以下概念:

  • 协程(Coroutine):可以暂停执行并稍后恢复的函数
  • 事件循环(Event Loop): 管理和执行所有异步任务的核心调度器
  • 任务(Task): 协程的包装器,使协程可以被调度执行
  • Awaitable对象 : 可以在await表达式中使用的对象
  • Future对象: 表示尚未完成的计算结果

1.1 异步函数定义

使用async def关键字定义一个异步函数:

python 复制代码
async def my_async_function():
    print("Start of the function")
    await some_async_operation()
    print("End of the function")

1.2 await表达式

在异步函数中,使用await关键字等待另一个异步操作完成:

python 复制代码
async def fetch_data():
    print("Fetching data...")
    await asyncio.sleep(1)  # 模拟一个耗时操作
    print("Data fetched!")

1.3 运行协程

要运行一个协程,需要使用asyncio.run()函数:

python 复制代码
import asyncio
async def main():
    await fetch_data()
asyncio.run(main())

1.4 并发执行多个协程

可以使用asyncio.gather()来并发执行多个协程:

python 复制代码
async def task1():
    print("Task 1 started")
    await asyncio.sleep(2)
    print("Task 1 completed")
async def task2():
    print("Task 2 started")
    await asyncio.sleep(1)
    print("Task 2 completed")
async def main():
    await asyncio.gather(task1(), task2())
asyncio.run(main())

生成器协程与原生协程

在Python 3.7及以上版本中,推荐使用async def定义的原生协程。旧版本中也可以使用生成器协程,但需要使用asyncio.coroutine装饰器:

python 复制代码
import asyncio
@asyncio.coroutine
def old_style_coroutine():
    print("Old style coroutine started")
    yield from asyncio.sleep(1)
    print("Old style coroutine completed")
async def main():
    await old_style_coroutine()
asyncio.run(main())

2. Python 协程常用的 API

2.1 asyncio.run()

简化了运行协程的方式,自动管理事件循环。
示例:

python 复制代码
import asyncio
async def main():
    await asyncio.sleep(1)
    print("Hello, asyncio!")
asyncio.run(main())

2.2 asyncio.sleep()

用于暂停协程执行,模拟I/O操作。
示例:

python 复制代码
async def delay_print(message):
    await asyncio.sleep(1)
    print(message)
async def main():
    await asyncio.gather(
        delay_print("Hello"),
        delay_print("World")
    )
asyncio.run(main())

2.3 asyncio.gather()

将多个协程组合为一个,同时运行。
示例:

python 复制代码
async def func1():
    print("Function 1 started")
    await asyncio.sleep(2)
    print("Function 1 done")
async def func2():
    print("Function 2 started")
    await asyncio.sleep(1)
    print("Function 2 done")
async def main():
    await asyncio.gather(func1(), func2())
asyncio.run(main())

2.4 asyncio.EventLoop

管理协程的执行循环。常用方法包括:

  • loop.run_forever(): 开始事件循环,直到loop.stop()被调用。
  • loop.run_until_complete(): 执行直到协程完成。
    示例:
python 复制代码
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()

事件循环通常用于网络编程。

2.5 asyncio.Task

用于管理协程任务。
示例:

python 复制代码
async def task_func():
    print("Task is running")
async def main():
    task = asyncio.create_task(task_func())
    await task
asyncio.run(main())

2.6 asyncio.Queue

在协程间安全地传递数据。
示例:

python 复制代码
async def producer(queue):
    await queue.put("Hello")
    await queue.put("World")
    await queue.put(None)
async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(item)
async def main():
    queue = asyncio.Queue()
    await asyncio.gather(
        producer(queue),
        consumer(queue)
    )
asyncio.run(main())

2.7 asyncio.Event

用于协程间的同步。
示例:

python 复制代码
import asyncio
import time

async def waiter(event, name):
    print(f"等待者 {name} 开始等待事件")
    start_time = time.time()
    await event.wait()  # 等待事件被设置
    end_time = time.time()
    print(f"等待者 {name} 检测到事件发生,等待时间: {end_time - start_time:.2f}秒")

async def event_setter(event, delay):
    print(f"事件设置者开始工作,将在 {delay} 秒后设置事件")
    await asyncio.sleep(delay)  # 模拟一些工作
    print("事件设置者正在设置事件...")
    event.set()  # 设置事件,唤醒所有等待者
    print("事件已设置")

async def main():
    # 创建一个事件对象
    event = asyncio.Event()
    
    # 创建多个等待者任务
    waiters = [
        waiter(event, "A"),
        waiter(event, "B"),
        waiter(event, "C")
    ]
    
    # 创建事件设置者任务
    setter = event_setter(event, 3)
    
    # 同时运行所有任务
    await asyncio.gather(*waiters, setter)

if __name__ == "__main__":
    print("程序开始运行")
    asyncio.run(main())
    print("程序运行结束")

实际应用场景:

  • 资源初始化:多个任务等待某个资源(如数据库连接)初始化完成;
  • 任务协调:多个工作线程等待某个条件满足后开始工作;
  • 事件通知:实现发布-订阅模式的基础。

2.8 asyncio.Condition

asyncio.Condtion()常被用来需要等待某个资源条件是否满足的情况。

下面使用协程实现了一个生产者-消费者模式。

python 复制代码
import asyncio
import random
import time

# 共享资源
shared_resource = []
MAX_ITEMS = 5

async def producer(condition, name):
    """生产者协程:向共享资源添加数据"""
    while True:
        # 模拟生产数据的时间
        await asyncio.sleep(random.uniform(0.5, 1.5))
        
        async with condition:
            # 如果资源已满,等待消费者消费
            while len(shared_resource) >= MAX_ITEMS:
                print(f"生产者 {name}: 资源已满,等待消费者...")
                await condition.wait()
            
            # 生产数据
            item = f"产品-{name}-{time.time()}"
            shared_resource.append(item)
            print(f"生产者 {name} 生产了: {item}")
            print(f"当前资源数量: {len(shared_resource)}")
            
            # 通知所有等待的消费者
            condition.notify_all()

async def consumer(condition, name):
    """消费者协程:从共享资源消费数据"""
    while True:
        # 模拟消费数据的时间
        await asyncio.sleep(random.uniform(0.5, 1.5))
        
        async with condition:
            # 如果资源为空,等待生产者生产
            while len(shared_resource) == 0:
                print(f"消费者 {name}: 资源为空,等待生产者...")
                await condition.wait()
            
            # 消费数据
            item = shared_resource.pop(0)
            print(f"消费者 {name} 消费了: {item}")
            print(f"当前资源数量: {len(shared_resource)}")
            
            # 通知所有等待的生产者
            condition.notify_all()

async def main():
    # 创建条件对象
    condition = asyncio.Condition()
    
    # 创建生产者和消费者任务
    producers = [
        producer(condition, f"P{i}") for i in range(2)
    ]
    consumers = [
        consumer(condition, f"C{i}") for i in range(3)
    ]
    
    # 运行所有任务
    await asyncio.gather(*producers, *consumers)

if __name__ == "__main__":
    print("生产者-消费者模型开始运行")
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("\n程序被用户中断")

执行结果:

你会发现 asyncio.Condtion()对象的 API·和 threading.Condition()很相似,这里就不再过多介绍了。

下面是这个程序的时序图,有助于你理解这个程序的逻辑。

使用场景:

  1. 生产者-消费者模式:协调生产者和消费者的工作,控制资源的使用和释放。
  2. 资源池管理:管理数据库连接池,控制线程池大小。
  3. 任务调度:实现任务队列,控制并发任务数量。
  4. 状态同步:等待特定状态变化,实现复杂的同步逻辑。
  5. 事件驱动系统:实现发布-订阅模式,处理异步事件通知。

asyncio.Event()的区别:

asyncio.Event()只有简单的标志位、只有设置和未设置两种状态、适合简单的同步场景。

asyncio.Condition()可以等待特定条件、支持选择性通知,比较适合复杂的同步场景。

2.9 第三方库

  • aiohttp: 异步HTTP客户端和服务器。
  • aiofiles: 异步文件处理。
  • asyncio_redis : 异步Redis客户端。
    示例(aiohttp):
python 复制代码
import aiohttp
import asyncio
async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()
async def main():
    async with aiohttp.ClientSession() as session:
        html = await fetch(session, 'https://example.com')
        print(html)
asyncio.run(main())

3. Python 协程的实现原理

3.1 协程和线程的区别

3.2 Python 协程的底层工作过程

Python协程的底层实现主要基于生成器和 yield 语句的扩展,以及通过事件循环来管理和调度这些协程,后来又引入了async/await语法。

下面的时序图展示了Python协程的底层工作过程,从协程创建到执行、挂起、恢复,直到完成的整个生命周期。

以下是图中关键步骤的详细解释:

  1. 创建阶段 :应用程序创建事件循环和协程对象。
    • 使用async def定义协程函数,创建事件循环
    • 协程在定义时并不执行,只是创建了一个协程对象
  2. 初始执行
    • 协程首次被事件循环执行时,会创建栈帧对象保存执行状态
    • 协程内部代码开始执行,直到遇到第一个await
  3. 挂起与调度
    • 当遇到await表达式时,协程会暂停执行并让出控制权
    • 底层通过yield机制实现,协程状态从GEN_RUNNING变为GEN_SUSPENDED
    • 被挂起的协程的栈帧会被保存,包括局部变量、指令指针等执行状态
  4. I/O操作与回调
    • 事件循环注册I/O回调,继续执行其他就绪的任务
    • 当I/O操作完成时,回调被触发,协程被标记为可恢复执行。 在 Python异步编程中,I/O操作完成通知事件循环的机制主要基于操作系统的I/O多路复用功能。I/O 多路复用机制,如 Linux 的 epoll、Windows 的 IOCP,这些机制允许一个进程监控多个文件描述符,等待它们变为可读或可写的状态。
  5. 恢复执行
    • 事件循环通过.send()方法恢复协程执行,传递I/O操作的结果
    • 协程从上次挂起的位置继续执行,状态从GEN_SUSPENDED变为GEN_RUNNING
  6. 完成或再次挂起
    • 如果协程执行完成,它会将结果返回给事件循环,状态变为GEN_COMPLETED
    • 如果遇到新的await点,协程会再次挂起,重复上述过程
相关推荐
belldeep1 分钟前
python:trimesh 用于 STL 文件解析和 3D 操作
python·3d·stl
Paran-ia8 分钟前
【2025版】Spring Boot面试题
java·spring boot·后端
顾一大人20 分钟前
dp自动化登陆之hCaptcha 验证码
爬虫·python·自动化
sufu10651 小时前
SpringAI更新:废弃tools方法、正式支持DeepSeek!
人工智能·后端
嘵奇1 小时前
Spring Boot拦截器详解:原理、实现与应用场景
java·spring boot·后端
Code_流苏1 小时前
《Python星球日记》 第71天:命名实体识别(NER)与关系抽取
python·深度学习·ner·预训练语言模型·关系抽取·统计机器学习·标注方式
点云SLAM1 小时前
Python中列表(list)知识详解(2)和注意事项以及应用示例
开发语言·人工智能·python·python学习·数据结果·list数据结果
国强_dev1 小时前
任意复杂度的 JSON 数据转换为多个结构化的 Pandas DataFrame 表格
开发语言·python
伊织code2 小时前
PyTorch API 7 - TorchScript、hub、矩阵、打包、profile
人工智能·pytorch·python·ai·矩阵·api
开开心心就好2 小时前
高效全能PDF工具,支持OCR识别
java·前端·python·pdf·ocr·maven·jetty