Python27_协程游戏理解

Python27_协程游戏理解

使用一个游戏来理解【协程】的执行过程

模拟代码

python 复制代码
import asyncio
import time


# ==================== 战争游戏:协程调度演示 ====================

class WarClick:
    """记录战争开始后的虚拟时间"""

    def __init__(self):
        self.start = time.time()

    def now(self):
        """返回第几天(1真实秒=1游戏天)"""
        return int(time.time() - self.start)

    def reportProcess(self, msg):
        print(f"[第{self.now():2d}天] {msg}")


war_click = WarClick()


# ==================== 任务定义 ====================

async def attackCity(cityName, needDay):
    """主线任务:攻城"""
    war_click.reportProcess(f"🏰 【主线】开始攻打 {cityName},预计{needDay}天")

    # 模拟攻城过程,分阶段报告
    for day in range(1, needDay + 1):
        await asyncio.sleep(1)  # 1秒=1天
        attack_progress = int(day / needDay * 100)
        if day < needDay:
            war_click.reportProcess(f"   ⏳ {cityName} 攻坚中... ({attack_progress}%)")

    war_click.reportProcess(f"✅ 【主线】{cityName} 攻陷!获得战利品!")
    return f"{cityName}控制权"


async def fetchAttackTsk(teamName, teamTsk, needDay):
    """支线任务:小分队行动"""
    war_click.reportProcess(f"🗡️ 【支线】{teamName} 出发执行「{teamTsk}」,预计{needDay}天")

    for day in range(1, needDay + 1):
        await asyncio.sleep(1)
        if day == needDay // 2:
            war_click.reportProcess(f"   📡 {teamName} 传回中期报告:任务进行中...")

    war_click.reportProcess(f"✅ 【支线】{teamName} 完成「{teamTsk}」!带回重要情报!")
    return f"{teamTsk}情报"


async def smartGeneral():
    """主调度逻辑"""
    print("=" * 60)
    print("⚔️  战争开始:协程调度演示")
    print("=" * 60)
    print("规则:1秒 = 1游戏天\n")

    # ========== 第1阶段:攻A ==========
    lootA = await attackCity("城池A", 3)
    print()

    # ========== 第2阶段:开启支线 + 继续主线 ==========
    war_click.reportProcess("📜 【战略决策】将军下令:两路小分队同时出发!")
    war_click.reportProcess("💡 【战术要点】将军不等待,主力立即转向城池B!")
    print()

    # 🔥 创建任务(派兵出发)但不等待!
    task_children_a = asyncio.create_task(fetchAttackTsk("小分队a", "烧毁粮仓", 8))
    task_children_b = asyncio.create_task(fetchAttackTsk("小分队b", "截断补给线", 4))

    # 给事件循环一个机会启动任务
    await asyncio.sleep(0)
    print()

    # ========== 第3阶段:攻B(与小分队并行)==========
    lootB = await attackCity("城池B", 5)
    print()

    # ========== 第4阶段:攻C(继续并行)==========
    war_click.reportProcess("📜 【战略决策】将军:'C城也要推,不等小分队了!'")
    print()
    lootC = await attackCity("城池C", 4)
    print()

    # ========== 第5阶段:收集团队结果 ==========
    war_click.reportProcess("📬 【战后清点】检查各小分队进展...")

    # 检查哪些已经完成
    task_children_finish_list = []
    task_children_running_list = [task_children_a, task_children_b]

    for task_children in list(task_children_running_list):
        if task_children.done():
            task_children_finish_list.append(task_children)
            task_children_running_list.remove(task_children)
            war_click.reportProcess(f"   ✅ 已完成:{task_children.result()}")

    # 等待未完成的(如果有)
    for task_children in task_children_running_list:
        war_click.reportProcess(f"   ⏳ 等待 {task_children.get_name() if hasattr(task_children, 'get_name') else '任务'} 完成...")
        task_result = await task_children
        war_click.reportProcess(f"   ✅ 刚刚完成:{task_result}")

    print()
    print("=" * 60)
    war_click.reportProcess("🏆 【战争结束】最终战果:")
    print(f"         主线收获:{lootA}、{lootB}、{lootC}")
    print(f"         支线收获:{task_children_b.result()}、{task_children_a.result()}")
    print("=" * 60)


# ==================== 运行 ====================

if __name__ == "__main__":
    # 设置事件循环策略(Windows兼容性)
    if asyncio.get_event_loop_policy().__class__.__name__ == 'WindowsSelectorEventLoopPolicy':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    # 运行战争模拟
    asyncio.run(smartGeneral())
  • 执行结果打印
console 复制代码
============================================================
⚔️  战争开始:协程调度演示
============================================================
规则:1秒 = 1游戏天

[第 0天] 🏰 【主线】开始攻打 城池A,预计3天
[第 1天]    ⏳ 城池A 攻坚中... (33%)
[第 2天]    ⏳ 城池A 攻坚中... (66%)
[第 3天] ✅ 【主线】城池A 攻陷!获得战利品!

[第 3天] 📜 【战略决策】将军下令:两路小分队同时出发!
[第 3天] 💡 【战术要点】将军不等待,主力立即转向城池B!

[第 3天] 🗡️ 【支线】小分队a 出发执行「烧毁粮仓」,预计8天
[第 3天] 🗡️ 【支线】小分队b 出发执行「截断补给线」,预计4天

[第 3天] 🏰 【主线】开始攻打 城池B,预计5天
[第 4天]    ⏳ 城池B 攻坚中... (20%)
[第 5天]    📡 小分队b 传回中期报告:任务进行中...
[第 5天]    ⏳ 城池B 攻坚中... (40%)
[第 6天]    ⏳ 城池B 攻坚中... (60%)
[第 7天]    📡 小分队a 传回中期报告:任务进行中...
[第 7天] ✅ 【支线】小分队b 完成「截断补给线」!带回重要情报!
[第 7天]    ⏳ 城池B 攻坚中... (80%)
[第 8天] ✅ 【主线】城池B 攻陷!获得战利品!

[第 8天] 📜 【战略决策】将军:'C城也要推,不等小分队了!'

[第 8天] 🏰 【主线】开始攻打 城池C,预计4天
[第 9天]    ⏳ 城池C 攻坚中... (25%)
[第10天]    ⏳ 城池C 攻坚中... (50%)
[第11天] ✅ 【支线】小分队a 完成「烧毁粮仓」!带回重要情报!
[第11天]    ⏳ 城池C 攻坚中... (75%)
[第12天] ✅ 【主线】城池C 攻陷!获得战利品!

[第12天] 📬 【战后清点】检查各小分队进展...
[第12天]    ✅ 已完成:烧毁粮仓情报
[第12天]    ✅ 已完成:截断补给线情报

============================================================
[第12天] 🏆 【战争结束】最终战果:
         主线收获:城池A控制权、城池B控制权、城池C控制权
         支线收获:截断补给线情报、烧毁粮仓情报
============================================================

这是一个绝佳的类比!让我用战争游戏的场景来讲透协程的工作原理。

战争设定

复制代码
主军队(主线程/事件循环):你亲自率领的主力部队
城池A、B、C:主线任务节点
支线任务:偷袭敌方粮道
小分队a:烧毁粮仓(耗时30天)
小分队b:截断补给线(耗时7天)

同步方式(愚蠢的将军)

python 复制代码
import time

def 攻打城池(name, days):
    print(f"【主线】开始攻城: {name},预计{days}天")
    time.sleep(days)  # ← 傻等!主力全军发呆
    print(f"【主线】{name}攻陷!")
    return f"{name}战利品"

def 小分队任务(name, days):
    print(f"【支线】{name}出发,耗时{days}天")
    time.sleep(days)  # ← 同样傻等,没人干活
    print(f"【支线】{name}完成!")
    return f"{name}情报"

# 愚蠢的调度:一件一件傻等
def 愚蠢将军():
    攻打城池("城池A", 3)
    
    # 派小分队(但将军傻等他们回来)
    小分队任务("烧粮仓", 30)  # 主力发呆30天!
    小分队任务("截补给", 7)   # 再发呆7天!
    
    攻打城池("城池B", 5)  # 37天后终于继续
    攻打城池("城池C", 4)

# 总耗时:3+30+7+5+4 = 49天

问题:将军派了小分队,却站在原地等他们回来报告,主力完全停滞!


协程方式(聪明的将军)

python 复制代码
import asyncio

async def 攻打城池(name, days):
    print(f"【主线】🏰 开始攻城: {name},预计{days}天")
    await asyncio.sleep(days)  # ← 挂起!主力去干别的
    print(f"【主线】✅ {name}攻陷!")
    return f"{name}战利品"

async def 小分队任务(name, days, 任务类型):
    print(f"【支线】🗡️ {name}出发({任务类型}),耗时{days}天")
    await asyncio.sleep(days)  # ← 挂起!小分队"离线"执行
    print(f"【支线】✅ {name}完成!")
    return f"{name}情报"

async def 聪明将军():
    start = asyncio.get_event_loop().time()
    
    # === 第1阶段:攻A ===
    await 攻打城池("城池A", 3)
    # 第3天:A攻陷
    
    # === 第2阶段:开启支线,但绝不傻等! ===
    print("\n📜 将军下令:两路小分队同时出发!")
    
    # 创建任务 = 派兵出发(只是登记,不立即执行细节)
    任务_烧粮 = asyncio.create_task(小分队任务("小分队a", 30, "烧粮仓"))
    任务_截补 = asyncio.create_task(小分队任务("小分队b", 7, "截补给线"))
    
    # 🔥 关键:将军不等待!立即继续主线
    print("💡 将军:'你们去干,我继续推B城!'\n")
    
    # === 第3阶段:攻B(与小分队并行)===
    # 第3-8天:攻B的同时,小分队b在干活(第3-10天)
    await 攻打城池("城池B", 5)
    # 第8天:B攻陷,但小分队还没完
    
    # === 第4阶段:攻C(继续并行)===
    print("\n💡 将军:'C城也要推,不等他们了!'")
    await 攻打城池("城池C", 4)
    # 第12天:C攻陷
    
    # === 第5阶段:检查支线 ===
    print(f"\n⏰ 当前时间:第{asyncio.get_event_loop().time() - start:.0f}天")
    print("📬 将军:'看看小分队进展...'")
    
    # 小分队b应该完成了(第10天完成),a还在跑(第33天完成)
    完成列表, 未完成列表 = await asyncio.wait(
        [任务_烧粮, 任务_截补], 
        timeout=0  # 不等待,只看当前状态
    )
    
    for task in 完成列表:
        print(f"   ✅ 已完成:{task.result()}")
    
    # 必须等小分队a(如果还没完)
    if 任务_烧粮 in 未完成列表:
        print("   ⏳ 小分队a还在烧粮,等他们...")
        粮仓情报 = await 任务_烧粮  # 这里才真正等待
        print(f"   ✅ 终于等到:{粮仓情报}")

# 总耗时:max(3+5+4, 30) = 30天(而不是49天!)
asyncio.run(聪明将军())

执行时间线图解

复制代码
天数:  0    3    8    10   12   30   33
       |----|----|----|----|----|----|
主线:  [攻A][----攻B----][-攻C-]      
       出发 |    派小分队a/b          等a完成
            |    ↓↓↓↓↓↓↓↓↓
支线a:      [----------烧粮仓----------] 30天
支线b:      [--截补给--] 7天
            ↑
            第3天同时出发,互不阻塞!

实际耗时 = 30天(由最长的小分队a决定)
         而不是 3+30+7+5+4 = 49天

核心机制对照

战争概念 协程概念 实际行为
将军发布命令 create_task() 登记任务,安排执行
将军继续推进 await主线任务 挂起等待,但事件循环调度其他任务
小分队独立行动 协程在后台"挂起" 不是真后台,是事件循环记得他们
战报送达 await task 恢复挂起点,获取返回值
将军统筹全局 事件循环(Event Loop) 单线程调度所有协程

关键洞察

python 复制代码
# 为什么小分队"不阻塞"却能在"单线程"跑?

async def 本质理解():
    """
    协程的"并行"是幻觉:
    
    第3天:将军派兵 → 写进日程表 → 继续走
    第4天:将军攻B → 小分队b"理论上"在进行第2天工作
    第5天:将军攻B → 小分队b"理论上"在进行第3天工作
    ...
    
    实际上CPU只在处理将军的代码
    但事件循环在"记账":小分队b应该完成了
    
    当将军代码await时,事件循环检查:
    "哦,b的7天到了,标记完成"
    """
    pass

将军的智慧

"我不需要亲自盯着小分队烧粮,我只需要记得我派了他们 ,然后相信时间到了他们会完成。在这期间,我的主力一刻不停!"

这就是 asyncio.create_task() + await 的组合艺术:启动不等待,需要时再收结果

相关推荐
gCode Teacher 格码致知1 小时前
Javascript提高:小数精度和随机数-由Deepseek产生
开发语言·javascript·ecmascript
Polar__Star1 小时前
Redis如何利用位图快速判断数据存在性
jvm·数据库·python
2301_817672262 小时前
CSS如何实现优雅的间距_使用CSS Grid控制盒模型间隙
jvm·数据库·python
你说咋整就咋整2 小时前
openGauss6.0.3 一主二从集群安装手册
数据库·python·gaussdb
Shorasul2 小时前
JavaScript中显式创建包装对象的后果与性能损耗
jvm·数据库·python
椰猫子2 小时前
Javaweb(Filter、Listener、AJAX、JSON)
java·开发语言
吕源林2 小时前
C#怎么实现EF Core迁移 C#如何用Entity Framework Core进行数据库迁移和更新表结构【数据库】
jvm·数据库·python
qq_206901392 小时前
JavaScript中箭头函数在对象字面量方法中的潜在错误
jvm·数据库·python
盛世宏博北京2 小时前
以太网温湿度传感器运维技巧,提升设备稳定性与使用寿命
开发语言·php·以太网温湿度传感器