Python协程学习笔记

一、协程基础概念

1.1 协程定义

协程(Coroutine)是用户空间的轻量级多任务机制,允许在单个线程内通过上下文切换实现协作式多任务处理,可暂停并恢复执行,模拟并发效果。

  • 核心区别:进程/线程由操作系统调度,协程由程序员控制,不依赖OS直接支持。

1.2 协程核心特点

特点 说明
资源占用少 仅需少量栈空间,通过状态保存/恢复实现切换,远低于进程/线程
切换开销小 完全在用户态完成,无需内核态上下文切换,开销极低
可暂停/恢复 遇I/O等耗时操作时主动暂停,释放CPU,适合I/O密集型任务

1.3 与进程/线程的对比

维度 进程 线程 协程
调度者 操作系统 操作系统 程序员(用户态)
资源占用 高(独立内存) 中(共享进程内存) 低(仅少量栈空间)
切换开销 极高(内核态) 中(内核态) 极低(用户态)
适用场景 CPU密集型、独立进程隔离 多任务并发(需处理线程安全) I/O密集型(如网络请求、文件读写)

二、Python协程实现方法

2.1 主流实现:async/await(Python 3.5+)

核心规则

  • async def 定义协程函数,调用时返回协程对象,不直接执行代码;
  • await 仅能在协程函数内使用,用于挂起协程,等待可等待对象(协程/Task/Future)完成。

代码示例

python 复制代码
import asyncio
import time

# 定义协程函数
async def say_after(delay, msg):
    await asyncio.sleep(delay)  # 挂起协程,等待延迟结束(非阻塞)
    print(msg)

# 主协程(程序入口)
async def main():
    print(f"开始时间: {time.strftime('%X')}")
    # 并发执行两个协程(需封装为Task,或用gather自动封装)
    await asyncio.gather(
        say_after(1, "hello"),
        say_after(2, "world")
    )
    print(f"结束时间: {time.strftime('%X')}")  # 总耗时≈2秒(并发效果)

# 启动协程(Python 3.7+推荐用法)
asyncio.run(main())

2.2 其他实现方式(了解即可)

  1. yield关键字(早期方式):
python 复制代码
def consumer():
    print("消费者准备接收数据")
    while True:
        data = (yield)  # 暂停并接收数据
        print(f"消费者接收: {data}")

def producer(consumer_gen):
    next(consumer_gen)  # 启动生成器
    for i in range(3):
        print(f"生产者发送: {i}")
        consumer_gen.send(i)  # 发送数据给消费者
    consumer_gen.close()  # 终止

# 运行
consumer_coro = consumer()
producer(consumer_coro)
  1. asyncio.coroutine装饰器(Python 3.4,3.8弃用):
python 复制代码
import asyncio

@asyncio.coroutine
def func():
    print("123")
    yield from asyncio.sleep(2)  # 类似await的作用
    print("456")

# 启动(Python 3.7前)
loop = asyncio.get_event_loop()
loop.run_until_complete(func())
loop.close()

三、协程核心组件

3.1 事件循环(Event Loop)

事件循环是协程的"调度中心",负责管理协程的执行、暂停与恢复,流程如下:

  1. 启动循环 → 2. 注册事件(如定时器、I/O) → 3. 循环等待事件 → 4. 执行/恢复协程 → 5. 任务完成后关闭。

版本差异

  • Python 3.7前:需手动获取循环
python 复制代码
import asyncio

async def func():
    await asyncio.sleep(1)
    print("执行完成")

# 3.7前用法
loop = asyncio.get_event_loop()
loop.run_until_complete(func())
loop.close()
  • Python 3.7+:asyncio.run() 自动创建/关闭循环(推荐)
python 复制代码
asyncio.run(func())  # 一行代码启动

3.2 Task对象

Task是协程的"封装器",将协程对象提交到事件循环并调度执行,支持查看任务状态、获取结果。

创建方式

Python版本 方法 示例
3.7前 asyncio.ensure_future() task = asyncio.ensure_future(func())
3.7+ asyncio.create_task()(推荐) task = asyncio.create_task(func())

代码示例

python 复制代码
import asyncio

async def func(name, delay):
    await asyncio.sleep(delay)
    return f"{name} 完成(延迟{delay}秒)"

async def main():
    # 创建Task
    task1 = asyncio.create_task(func("任务1", 2))
    task2 = asyncio.create_task(func("任务2", 1))
    
    # 等待Task完成并获取结果
    result1 = await task1
    result2 = await task2
    print(result1, result2)

asyncio.run(main())

四、协程通信机制(asyncio.Queue

协程间通过消息队列通信,asyncio.Queue 提供三种队列类型,均支持异步操作(await put()/await get())。

4.1 三种队列对比

队列类型 特点 适用场景
Queue 先进先出(FIFO) 普通顺序通信(如生产者-消费者)
LifoQueue 后进先出(LIFO) 栈式场景(如任务优先级反转)
PriorityQueue 按优先级排序(元组第一个元素为优先级) 高优先级任务优先处理

4.2 代码示例(生产者-消费者模型)

python 复制代码
import asyncio

# 生产者协程:向队列放数据
async def producer(queue):
    for i in range(1, 4):
        print(f"生产者放入: {i}")
        await queue.put(i)  # 队列满时阻塞
        await asyncio.sleep(1)  # 模拟生产耗时
    await queue.put(None)  # 发送结束信号

# 消费者协程:从队列取数据
async def consumer(queue):
    while True:
        item = await queue.get()  # 队列空时阻塞
        if item is None:  # 接收结束信号
            queue.task_done()  # 标记任务完成
            break
        print(f"消费者取出: {item}")
        queue.task_done()  # 标记任务完成

async def main():
    queue = asyncio.Queue(maxsize=2)  # 最大容量2
    await asyncio.gather(producer(queue), consumer(queue))
    await queue.join()  # 等待所有任务处理完毕
    print("所有任务完成")

asyncio.run(main())

五、协程同步机制

协程共享线程资源,需同步机制避免数据竞争,asyncio 提供四种核心同步工具:

5.1 Lock(互斥锁)

确保同一时间仅一个协程访问共享资源,类似线程的threading.Lock

代码示例

python 复制代码
import asyncio

async def worker(lock, id):
    while True:
        async with lock:  # 自动获取/释放锁,避免死锁
            print(f"Worker {id} 正在执行")
            await asyncio.sleep(1)  # 模拟耗时操作

async def main():
    lock = asyncio.Lock()
    # 并发运行2个worker,同一时间仅1个执行
    await asyncio.gather(worker(lock, 1), worker(lock, 2))

asyncio.run(main())

5.2 Semaphore(信号量)

控制同时访问资源的协程数量,通过内部计数器实现(计数器>0可获取,=0阻塞)。

代码示例(停车场场景)

python 复制代码
import asyncio

async def car(semaphore, car_id):
    print(f"车辆{car_id} 等待停车位")
    async with semaphore:  # 获取信号量(占用1个车位)
        print(f"车辆{car_id} 进入停车场")
        await asyncio.sleep(2)  # 停留2秒
        print(f"车辆{car_id} 离开停车场")

async def main():
    semaphore = asyncio.Semaphore(3)  # 仅3个停车位
    cars = [car(semaphore, i) for i in range(1, 6)]  # 5辆车
    await asyncio.gather(*cars)

asyncio.run(main())

5.3 Event(事件)

用于协程间"通知",一个协程设置事件,多个协程等待事件触发。

代码示例

python 复制代码
import asyncio

async def producer(event):
    print("生产者准备数据...")
    await asyncio.sleep(2)  # 模拟准备耗时
    event.set()  # 触发事件(通知消费者)
    print("生产者已通知")

async def consumer(event):
    print("消费者等待通知...")
    await event.wait()  # 阻塞,直到事件被设置
    print("消费者收到通知,开始处理")

async def main():
    event = asyncio.Event()
    await asyncio.gather(producer(event), consumer(event))

asyncio.run(main())

5.4 Condition(条件变量)

结合LockEvent功能,支持协程等待特定条件成立后再执行(如"队列非空")。

代码示例

python 复制代码
import asyncio

async def producer(cond):
    while True:
        async with cond:  # 获取锁
            cond.notify_all()  # 通知所有等待的消费者
        await asyncio.sleep(1)

async def consumer(cond, id):
    while True:
        async with cond:
            print(f"消费者{id} 等待通知")
            await cond.wait()  # 释放锁并等待通知
            print(f"消费者{id} 收到通知")

async def main():
    cond = asyncio.Condition()
    tasks = [
        asyncio.create_task(producer(cond)),
        *[asyncio.create_task(consumer(cond, i)) for i in range(3)]
    ]
    await asyncio.wait(tasks)

asyncio.run(main())

六、协程与线程池/进程池结合

协程适合I/O密集型任务,CPU密集型任务需结合进程池 (避免GIL锁限制)。asyncio 提供工具将线程池/进程池任务转为可等待对象。

6.1 核心方法

  1. asyncio.wrap_future():将concurrent.futures.Future转为asyncio.Future
  2. loop.run_in_executor():直接在执行器中运行同步函数,返回asyncio.Future(推荐)。

6.2 代码示例(协程+进程池)

python 复制代码
import asyncio
import concurrent.futures
import time

# CPU密集型同步函数(耗时计算)
def cpu_intensive_task(n):
    result = sum(i*i for i in range(n))
    print(f"计算完成: sum(0~{n-1})² = {result}")
    return result

async def main():
    # 获取当前事件循环
    loop = asyncio.get_running_loop()
    
    # 使用进程池执行CPU密集型任务
    with concurrent.futures.ProcessPoolExecutor() as pool:
        # 提交任务到进程池,返回asyncio.Future
        task1 = loop.run_in_executor(pool, cpu_intensive_task, 10**6)
        task2 = loop.run_in_executor(pool, cpu_intensive_task, 10**6)
        
        # 等待结果
        result1 = await task1
        result2 = await task2
        print(f"最终结果: {result1}, {result2}")

if __name__ == "__main__":  # 进程池必须在主模块中运行
    start = time.time()
    asyncio.run(main())
    print(f"总耗时: {time.time() - start:.2f}秒")

七、关键总结

  1. 适用场景:协程优先用于I/O密集型任务(如网络请求、文件读写),CPU密集型需结合进程池;
  2. 核心关键字async def(定义协程)、await(挂起协程);
  3. 版本推荐 :Python 3.7+使用asyncio.run()asyncio.create_task()简化代码;
  4. 同步与通信 :通过asyncio.Queue通信,Lock/Semaphore/Event/Condition实现同步。
相关推荐
网安小白的进阶之路1 小时前
B模块 安全通信网络 第一门课 园区网实现与安全-3-项目实战
网络·安全
大雨淅淅1 小时前
【编程语言】Kotlin:从新手到大神的进阶之路
android·开发语言·kotlin
爱笑的眼睛111 小时前
Flask应用API深度开发:从单体架构到微服务设计模式
java·人工智能·python·ai
AI小云1 小时前
【数据操作与可视化】Matplotlib绘图-常用操作
python·数据可视化
j***12151 小时前
计算机体系结构期末复习3:GPU架构及控制流问题
java·开发语言·架构
zore_c1 小时前
【C语言】文件操作详解1(文件的打开与关闭)
c语言·开发语言·数据结构·c++·经验分享·笔记·算法
ICT技术最前线1 小时前
内网穿透目前最好的解决方案是什么?
网络·内网穿透
木婉清fresh1 小时前
测开python高频面试精选100题
开发语言·python·面试
彼岸花开了吗1 小时前
构建AI智能体:四十、K-Means++与RAG的融合创新:智能聚类与检索增强生成的深度应用
人工智能·python