Python进阶:生成器与协程,高效并发编程的核心实践

在Python并发编程的演进中,进程、线程、生成器、协程是绕不开的核心概念。进程是资源隔离的最小单位,线程是CPU调度的基本单元,而生成器是协程的基础,协程则是轻量级的用户态并发方案。本文将通过5个实战代码文件,从生成器推导式到协程实战,逐步拆解核心逻辑,并对比进程、线程的差异,帮你彻底掌握高效并发编程。

一、核心概念前置:进程 vs 线程 vs 协程

先理清三者的本质区别,这是理解后续代码的基础:

特性 进程 线程 协程
资源占用 高,独立内存空间 中,共享进程内存 极低,共享线程栈
切换开销 极大(操作系统调度,内核态) 较大(操作系统调度,内核态) 极小(用户态自行切换)
并发能力 多核并行,适合CPU密集型 受GIL限制,Python中无真正并行 单线程并发,适合IO密集型
实现难度 低(基于生成器/asyncio)
核心依赖 os.fork/multiprocessing threading 生成器/yield/asyncio

核心结论 :Python中线程受GIL(全局解释器锁)限制,无法实现多核并行;协程不依赖操作系统调度 ,在单线程内完成任务切换,是IO密集型场景(爬虫、文件读写、网络请求)的最优解。而生成器是协程的底层基石


二、实战代码:从生成器到协程

01_回顾推导式.py

推导式是Python的语法糖,支持快速创建列表、字典、集合,也是理解生成器推导式的基础。

python 复制代码
# 1. 列表推导式:立即创建所有元素,占用内存大
list_comp = [i for i in range(5)]
print("列表推导式:", list_comp)  # 输出:[0,1,2,3,4]

# 2. 字典推导式
dict_comp = {i: i*2 for i in range(3)}
print("字典推导式:", dict_comp)  # 输出:{0:0,1:2,2:4}

# 3. 集合推导式
set_comp = {i for i in range(3)}
print("集合推导式:", set_comp)  # 输出:{0,1,2}

核心特点 :推导式会立即生成所有数据,适合数据量小的场景;数据量过大时,会占用大量内存。


02_生成器推导式.py

生成器推导式是惰性求值 的迭代器,语法和列表推导式一致,仅将[]改为()不立即生成数据,节省内存。

python 复制代码
# 生成器推导式:惰性生成元素,内存占用极低
gen = (i for i in range(5))
print("生成器对象:", gen)  # 输出:<generator object <genexpr> at 0xxxx>

# 遍历生成器:每次调用next()才生成一个元素
print(next(gen))  # 0
print(next(gen))  # 1

# 循环遍历:自动调用next(),直到无元素
for num in gen:
    print("循环遍历:", num)  # 2,3,4

核心特点

  1. 惰性求值:用到才生成,不占用额外内存;
  2. 只能遍历一次,遍历完后自动销毁;
  3. 是Python中轻量级的迭代器实现。

03_yield生成器.py

yield是生成器的核心关键字,替代return,让函数变成生成器函数,支持暂停/恢复执行。

python 复制代码
# 定义生成器函数:包含yield关键字
def generator_func():
    yield 1  # 暂停,返回1
    yield 2  # 暂停,返回2
    yield 3  # 暂停,返回3

# 创建生成器对象
gen = generator_func()
print("生成器函数对象:", gen)  # <generator object generator_func at 0xxxx>

# 手动获取元素
print(next(gen))  # 1
print(next(gen))  # 2

# 循环获取剩余元素
for num in gen:
    print("剩余元素:", num)  # 3

核心原理

  • 调用next()时,函数执行到yield暂停,返回当前值;
  • 再次调用next(),从暂停位置继续执行,直到下一个yield
  • yield可执行时,抛出StopIteration异常。

进阶:带参数的生成器(send方法)

生成器不仅能产出值,还能接收外部传入的值,这是协程的基础:

python 复制代码
def param_gen():
    while True:
        # 接收外部send的值,赋值给x
        x = yield
        print(f"接收到的值:{x}")

gen = param_gen()
next(gen)  # 预激活生成器
gen.send(10)  # 发送10,输出:接收到的值:10
gen.send(20)  # 发送20,输出:接收到的值:20

04_协程的应用.py

协程是用户态的轻量级线程 ,基于生成器的yield暂停/恢复特性实现,无需操作系统调度,单线程内实现并发。

Python3.5+推荐使用async/await语法(底层仍依赖生成器),实战如下:

python 复制代码
import asyncio
import time

# 定义协程函数:async关键字修饰
async def task(name, delay):
    print(f"协程任务{name}开始,耗时{delay}秒")
    # await:暂停当前协程,执行其他协程,实现非阻塞等待
    await asyncio.sleep(delay)
    print(f"协程任务{name}完成")

# 主协程:调度多个协程
async def main():
    # 创建协程任务列表
    tasks = [
        asyncio.create_task(task("A", 2)),
        asyncio.create_task(task("B", 1)),
    ]
    # 并发执行所有任务
    await asyncio.gather(*tasks)

if __name__ == "__main__":
    start = time.time()
    # 运行协程
    asyncio.run(main())
    end = time.time()
    print(f"总耗时:{end - start:.2f}秒")

运行结果

复制代码
协程任务A开始,耗时2秒
协程任务B开始,耗时1秒
协程任务B完成
协程任务A完成
总耗时:2.01秒

核心逻辑

  1. async定义协程函数,不能直接执行,需放入事件循环;
  2. await:遇到IO等待时,自动切换到其他协程,不阻塞线程;
  3. 两个任务总耗时≈最长任务耗时,实现真正的并发。

05_示例体验.py

综合对比:同步执行 vs 协程并发,直观感受协程的效率提升。

python 复制代码
import asyncio
import time

# 同步函数:阻塞执行
def sync_task(name, delay):
    print(f"同步任务{name}开始,耗时{delay}秒")
    time.sleep(delay)
    print(f"同步任务{name}完成")

# 协程函数:非阻塞并发
async def async_task(name, delay):
    print(f"协程任务{name}开始,耗时{delay}秒")
    await asyncio.sleep(delay)
    print(f"协程任务{name}完成")

# 1. 同步执行测试
print("===== 同步执行 =====")
start = time.time()
sync_task("A", 2)
sync_task("B", 1)
print(f"同步总耗时:{time.time() - start:.2f}秒\n")

# 2. 协程并发测试
print("===== 协程并发 =====")
async def main():
    await asyncio.gather(
        async_task("A", 2),
        async_task("B", 1),
    )

start = time.time()
asyncio.run(main())
print(f"协程总耗时:{time.time() - start:.2f}秒")

运行结果

复制代码
===== 同步执行 =====
同步任务A开始,耗时2秒
同步任务A完成
同步任务B开始,耗时1秒
同步任务B完成
同步总耗时:3.01秒

===== 协程并发 =====
协程任务A开始,耗时2秒
协程任务B开始,耗时1秒
协程任务B完成
协程任务A完成
协程总耗时:2.01秒

核心结论:同步执行总耗时=所有任务耗时之和,协程并发总耗时≈最长任务耗时,效率提升显著。


三、生成器与协程的核心关系

  1. 生成器是协程的底层基础 :协程的暂停/恢复,依赖生成器yield的特性;
  2. 生成器侧重数据生成:逐个产出数据,节省内存;
  3. 协程侧重任务并发:在单线程内切换IO任务,提升并发效率。

四、适用场景总结

  1. 进程:CPU密集型任务(大数据计算、视频编码),利用多核;
  2. 线程:IO密集型+低并发场景,受GIL限制,效率一般;
  3. 生成器:大数据量迭代(读取大文件、海量数据生成);
  4. 协程:高并发IO密集型场景(爬虫、API接口、WebSocket、文件批量读写)。

总结

本文通过5个实战代码,从基础推导式过渡到生成器,再基于生成器实现协程,同时对比了进程、线程、协程的核心差异:

  1. 推导式是语法糖,立即生成数据;生成器推导式惰性求值,节省内存;
  2. yield是生成器核心,支持暂停/恢复,是协程的底层依赖;
  3. 协程基于用户态切换,无操作系统开销,是Python高并发IO的最优解;
  4. 进程/线程适合不同场景,协程则是轻量级并发的终极选择。

掌握生成器和协程,能让你的Python代码在IO密集型场景下,用极低的资源实现超高并发,这也是Python进阶编程的必备技能。

相关推荐
XiaoQiao6669992 小时前
python 简单题目练手【详解版】【1】
开发语言·python
ZC跨境爬虫2 小时前
极验滑动验证码自动化实战:背景提取、缺口定位与Playwright滑动模拟
前端·爬虫·python·自动化
智算菩萨2 小时前
【Python图像处理】2 数字图像基础与Python图像表示
开发语言·图像处理·python
xiaoshuaishuai83 小时前
Git二分法定位Bug
开发语言·python
2401_835792543 小时前
FastAPI 速通
windows·python·fastapi
YMWM_4 小时前
export MPLBACKEND=Agg命令使用
linux·python
派大星~课堂4 小时前
【力扣-148. 排序链表】Python笔记
python·leetcode·链表
微涼5304 小时前
【Python】在使用联网工具时需要的问题
服务器·python·php
小白菜又菜4 小时前
Leetcode 657. Robot Return to Origin
python·leetcode·职场和发展