概念
协程是一种用户态的轻量级线程,允许程序在执行过程中主动暂停和恢复,而不需要像传统线程那样依赖操作系统的调度。
协程就是子程序在执行是中断并转去执行别的子程序,在适当的时候有返回来继续执行。
这种子程序间的跳转不是函数调用,也不是多线程执行,所以省去了线程切换的开销,效率很高,并且不需要多线程间的锁机制,不会发生写冲突。
协程关键特性
- 暂停与恢复:协程可以在执行过程中暂停,保存当前的状态(如变量的值、执行的位置等),然后再需要的时候恢复执行,从暂停的位置继续往下执行。
- 轻量级:创建和切换协程的开销非常小,远远小于创建和切换线程的开销。因此,在同一个线程中可以同时运行大量协程。
- 协作式调度:协程的执行权转移时由程序主动控制的,而不是操作系统强制调用。这意味着协程之间可以通过协作的方式来高效地利用CPU资源。
- 单线程内并发:多个协程复用同一个线程,适合IO密集型场景。
与线程的对比
特性 | 线程(Thread) | 协程(Coroutine) |
---|---|---|
调度方式 | 操作系统内核调度(抢占式) | 程序主动控制 |
创建开销 | 高(需要内核资源) | 极低(仅用户态数据结构) |
内存占用 | 每个线程约1MB(默认栈大小) | 每个协程约KB级别(可动态调整) |
切换开销 | 高(涉及内核态与用户态切换) | 极低(仅寄存器和栈指针保存) |
并发性 | 多线程并行(多核CPU) | 单线程内并发(无法并行) |
应用场景
- 高并发网络服务:如Web服务器。
- 数据流处理:如管道、过滤器模型的异步实现。
- 游戏AI:管理多个NPC的行为逻辑,避免阻塞主线程。
- 定时任务调度:轻量级地实现任务队列和定时器。
代码示例
暂停恢复场景示例
import asyncio
async def controlled_task(event):
print("协程启动,执行第一步...")
await event.wait() # 暂停点:等待事件触发
print("协程恢复,执行第二步...")
async def main():
event = asyncio.Event()
task = asyncio.create_task(controlled_task(event))
print("主程序:先做其他事情...")
await asyncio.sleep(2) # 模拟耗时操作
print("主程序:触发协程继续执行")
event.set() # 恢复协程
await task # 等待协程完成
asyncio.run(main())
游戏AI应用场景
import asyncio
import random
# NPC 基类
class NPC:
def __init__(self, name):
self.name = name
self.state = "idle" # 初始状态
async def run(self):
"""NPC 主行为循环,使用协程实现非阻塞"""
while True:
if self.state == "idle":
await self.idle() # 执行待机行为,await 会暂停协程
elif self.state == "walking":
await self.walking() # 执行行走行为
elif self.state == "talking":
await self.talking() # 执行对话行为
await asyncio.sleep(0) # 强制让出控制权给其他协程,确保公平调度
async def idle(self):
"""待机状态:随机切换到行走或对话状态"""
print(f"{self.name} 正在待机...")
await asyncio.sleep(random.uniform(1, 3)) # 模拟思考时间,期间允许其他协程运行
self.state = random.choice(["walking", "talking"]) # 随机切换状态
async def walking(self):
"""行走状态:随机移动一段时间后待机"""
print(f"{self.name} 正在散步...")
await asyncio.sleep(random.uniform(2, 5)) # 模拟行走时间,非阻塞等待
self.state = "idle" # 恢复待机状态
async def talking(self):
"""对话状态:与玩家对话,可能被打断"""
print(f"{self.name} 开始对话:'你好,冒险者!'")
await asyncio.sleep(random.uniform(3, 4)) # 模拟对话时间
print(f"{self.name} 结束对话")
self.state = "idle" # 对话结束后恢复待机
# 游戏主循环
async def game_loop():
"""游戏主循环,管理所有 NPC 和玩家交互"""
# 创建 NPC
npc1 = NPC("村民A")
npc2 = NPC("商人B")
npc3 = NPC("卫兵C")
# 启动所有 NPC 的行为协程
# asyncio.create_task() 将协程包装为 Task 对象,加入事件循环并发执行
npc_tasks = [
asyncio.create_task(npc.run()) # 为每个 NPC 创建独立的协程任务
for npc in [npc1, npc2, npc3]
]
# 主循环:处理玩家输入、渲染画面等
try:
while True:
# 模拟玩家输入处理
await asyncio.sleep(0.1) # 100ms 渲染间隔,控制循环执行频率
# 玩家主动与NPC交互如进行对话
if random.random() < 0.1: # 10% 的概率触发交互触发对话
npc = random.choice([npc1, npc2, npc3])
print(f"[玩家]主动 与 {npc.name} 交互!")
npc.state = "talking" # 直接修改 NPC 状态,协程下次检查时会响应
asyncio.create_task(npc.talking()) # 异步启动对话
#npc.talking() 不能进行npc.talking()因为这个是同步操作,会阻塞主线程,导致游戏卡顿
except KeyboardInterrupt: # 捕获 Ctrl+C 中断
# 退出
for task in npc_tasks:
task.cancel() # 向所有 NPC 协程发送取消请求
await asyncio.gather(*npc_tasks, return_exceptions=True) # 等待所有任务完成取消流程
# 运行游戏
asyncio.run(game_loop()) # Python 3.7+ 的入口函数,创建并启动事件循环,运行主协程