理解协程
什么是协程
由于GIL的存在,导致Python多线程性能甚至比单线程更糟
GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行。[1]即便在多核心处理器上,使用 GIL 的解释器也只允许同一时间执行一个线程。
于是出现了协程(Coroutine)这么个东西
协程: 协程,又称微线程,纤程,英文名Coroutine。协程的作用,是在执行函数A时,可以随时中断,去执行函数B,然后中断继续执行函数A(可以自由切换)。但这一过程并不是函数调用(没有调用语句),这一整个过程看似像多线程,然而协程只有一个线程执行.
协程相比于线程和进程,具有以下优点:
-
轻量级:协程的上下文切换成本很小,可以在单线程内并发执行大量的协程。
-
低延迟:协程的执行过程中,没有线程切换的开销,也没有加锁解锁的开销,可以更快地响应外部事件。
-
高效性:协程的代码通常比多线程和多进程的代码更加简洁和可读,维护成本更低
协程的两种实现方式
生成器协程
生成器协程(Generator Coroutine)是指使用生成器函数来实现的协程。生成器函数是一种特殊的函数,其返回一个生成器对象,可以通过 yield 语句暂停函数的执行,然后在下一次调用生成器对象的 「next」() 方法时继续执行
# 一个简单的生成器协程的示例,其中包含一个生成器函数 coroutine 和一个简单的异步 I/O 操作
import asyncio
def coroutine():
print('Coroutine started')
while True:
result = yield
print('Coroutine received:', result)
async def main():
print('Main started')
c = coroutine()
next(c)
c.send('Hello')
await asyncio.sleep(1)
c.send('World')
print('Main finished')
asyncio.run(main())
################# 结果输出 #######################
Main started
Coroutine started
Coroutine received: Hello
Coroutine received: World
Main finished
执行过程:
-
main 函数开始执行,打印出 Main started。
-
创建一个生成器对象 c,调用 next(c) 使其执行到第一个 yield 语句处暂停。
-
使用 c.send('Hello') 恢复生成器函数的执行,并将 'Hello' 作为生成器函数的返回值。
-
在等待1秒钟的过程中,main 函数暂停执行,等待事件循环发起下一次任务。
-
在等待1秒钟后,使用 c.send('World') 继续执行生成器函数,并将 'World' 作为生成器函数的返回值。
-
main 函数恢复执行,打印出 Main finished。
在上面的代码中,使用生成器函数 coroutine 实现了一个简单的协程。生成器函数通过使用 yield 语句暂停函数的执行,然后可以通过 send 方法恢复函数的执行,并将值传递给生成器函数。通过这种方式,可以使用生成器函数实现异步并发。在上面的示例中,使用生成器函数接收并打印异步 I/O 操作的结果。
原生协程
原生协程是通过使用 async/await 关键字来定义的,与生成器协程不同,它们可以像普通函数一样使用 return 语句返回值,而不是使用 yield 语句
# 一个简单的原生协程示例,其中包含一个 async 关键字修饰的协程函数 coroutine 和一个简单的异步 I/O 操作:
import asyncio
async def coroutine():
print('Coroutine started')
await asyncio.sleep(1)
print('Coroutine finished')
async def main():
print('Main started')
await coroutine()
print('Main finished')
asyncio.run(main())
################# 结果输出 #######################
Main started
Coroutine started
Coroutine finished
Main finished
执行过程:
-
main 函数开始执行,打印出 Main started。
-
调用 coroutine 函数,将其作为一个协程对象运行。
-
在 coroutine 函数中,打印出 Coroutine started。
-
在 coroutine 函数中,使用 await asyncio.sleep(1) 暂停函数的执行,等待1秒钟。
-
在1秒钟后,恢复 coroutine 函数的执行,并打印出 Coroutine finished。
-
main 函数恢复执行,打印出 Main finished。
在上面的代码中,使用 async 关键字定义了一个原生协程函数 coroutine,并在其中使用 await 关键字来暂停函数的执行,等待异步 I/O 操作的完成。通过这种方式,可以在原生协程中编写异步并发代码,从而提高代码的性能和效率
两种协程对比
区别
-
定义方式不同:原生协程使用 async/await 关键字来定义,而生成器协程使用 yield 关键字来定义。
-
返回方式不同:原生协程使用 return 语句来返回结果,而生成器协程使用 yield 语句来返回结果。
-
调用方式不同:原生协程使用 await 关键字来调用,而生成器协程使用 yield from 或 yield 语句来调用。
-
内部实现不同:原生协程通过 asyncio 库来实现,而生成器协程是 Python 语言内置的特性
生成器协程的优点
-
兼容性好:生成器协程是 Python 2 和 Python 3 都支持的特性。
-
可读性好:生成器协程使用 yield 关键字来实现,代码逻辑清晰易懂。
-
异常处理方便:生成器协程在处理异常时比较方便,可以使用 try/except 语句来处理。
生成器协程的缺点
-
性能相对较低:生成器协程需要创建生成器对象,也需要通过 yield 语句来控制函数的执行流程,因此处理异步操作时性能相对较低。
-
功能有限:生成器协程不能像原生协程一样支持异步 I/O 操作和任务处理
原生协程的优点:
-
代码简洁易懂:使用 async/await 关键字,可以编写出更简洁易懂的协程代码。
-
-
支持异步 I/O 和任务处理:原生协程可以支持异步 I/O 操作和并发任务处理,可以在处理异步操作时更加灵活
原生协程的缺点
-
兼容性差:原生协程是 Python 3.5 版本之后才引入的新特性,因此在旧版本的 Python 中无法使用。
-
异常处理不方便:原生协程在处理异常时比较麻烦,需要使用 try/except 语句来处理
-
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】