云计算百科
云计算领域专业知识百科平台

Python Agent 踩坑实录:我的多 Agent 系统在深夜互相等死

Python Agent 踩坑实录:我的多 Agent 系统在深夜互相等死

摘要:本文从一次"多 Agent 工作流在深夜突然冻结、CPU 归零但任务永远不结束"的事故出发,分析 Python Agent 开发中多 Agent 通信死锁的四个经典陷阱:父-子 Agent 信号量嵌套死锁、跨 Agent 消息队列循环等待、lazy-import 非重入锁自死锁、以及共享状态读-改-写竞态导致的"活锁"。文章给出带超时保护的锁获取、死锁检测与恢复、以及循环终止谓词的完整修复方案,并延伸到多 Agent 工作流的并发控制设计原则。附完整可复用代码和排查清单,适合正在学 Python + Agent 的开发者避坑。

文章目录

  • Python Agent 踩坑实录:我的多 Agent 系统在深夜互相等死
    • 一、问题背景:任务没有崩溃,但永远不结束
    • 二、最小复现代码:十行代码复现多 Agent 死锁
    • 三、原因分析:四个隐蔽的多 Agent 死锁陷阱
      • 陷阱一:父-子 Agent 信号量嵌套死锁
      • 陷阱二:跨 Agent 消息队列循环等待
      • 陷阱三:lazy-import 非重入锁自死锁
      • 陷阱四:共享状态读-改-写竞态导致的“活锁”
      • 一句话总结
    • 四、修复方案:超时保护 + 循环检测 + 终止谓词
      • 核心思路
      • 第一层:带超时保护的锁获取
      • 第二层:死锁检测与恢复
      • 第三层:循环终止谓词
    • 五、Agent 开发中的特殊场景
      • 场景一:LangGraph 工作流的循环失控
      • 场景二:AutoGen GraphFlow 状态损坏的恢复
      • 场景三:多 Agent 系统的并发控制清单
      • 踩坑检查清单
    • 六、排查技巧:如何发现多 Agent 死锁
      • 方法一:用 faulthandler 转储所有线程栈
      • 方法二:用 asyncio debug 模式检测阻塞
      • 方法三:监控等待关系图
      • 方法四:统计每个 Agent 的阻塞时间
    • 七、总结

一、问题背景:任务没有崩溃,但永远不结束

最近在维护一个多 Agent 工作流系统,用 Python 实现了"规划 Agent + 执行 Agent"的父子协作架构。

架构是这样的:

  • 规划 Agent 接收用户任务,拆解为子任务;
  • 每个子任务分发给一个执行 Agent;
  • 执行 Agent 完成后,结果回传给规划 Agent 汇总。

某个周一早上,我发现周末期间有几十个任务卡在了“运行中”状态——不是崩溃,不是报错,而是永远不结束。

排查日志后发现问题:

  • 所有卡住的任务都停在同一个地方:“等待子 Agent 完成”;
  • CPU 使用率几乎为零(没有在计算,就是在等);
  • 进程活着,线程活着,事件循环活着,但没有任何协程在推进;
  • kill -9 之后重启才能恢复。

排查后发现,这不是一个 Bug,而是三个 Bug 叠加形成的完美死锁:

  • 父 Agent 持有并发槽位,等待子 Agent 完成;
  • 子 Agent 需要获取并发槽位才能启动,但槽位被父 Agent 占着;
  • 两个 Agent 都不释放,形成闭环。
  • gptme 项目的 PR #2892 记录了完全一样的问题:父 executor 在 _sem.acquire() 之后调用 t.join() 阻塞等待子线程,而子线程也在尝试 _sem.acquire()。max_concurrent=1 时这是干净的死锁;max_concurrent=N 时,N 个同时嵌套的顺序规划器会全部死锁。

    这就是本文要讲的坑——多 Agent 系统的死锁和单线程死锁的本质不同,它发生在“逻辑协调层”而不是“资源锁层” 。

    二、最小复现代码:十行代码复现多 Agent 死锁

    先看错误版本:

    import asyncio
    import threading

    # 全局并发信号量:限制同时运行的 Agent 数量
    _sem = threading.Semaphore(1)

    async def subagent(task: str):
    """子 Agent:需要获取信号量才能运行"""
    print(f"[SUB] 尝试获取信号量: {task}")
    _sem.acquire() # 阻塞等待信号量
    print(f"[SUB] 获取成功: {task}")
    await asyncio.sleep(0.1)
    _sem.release()
    return f"{task} 完成"

    async def parent_agent(task: str):
    """父 Agent:持有信号量,等待子 Agent"""
    _sem.acquire() # 父 Agent 先获取信号量
    print(f"[PARENT] 持有信号量,开始规划: {task}")

    # 父 Agent 调用子 Agent
    sub_task = asyncio.create_task(subagent("子任务-1"))

    # 父 Agent 阻塞等待子任务完成——但信号量还在父 Agent 手里!
    result = await sub_task
    print(f"[PARENT] 子任务完成: {result}")

    _sem.release()
    return result

    async def main():
    await parent_agent("主任务")

    asyncio.run(main())

    输出:

    [PARENT] 持有信号量,开始规划: 主任务
    [SUB] 尝试获取信号量: 子任务-1
    (然后永远卡住,没有任何输出)

    问题就在这两行:

    _sem.acquire() # 父 Agent 获取了信号量
    result = await sub_task # 父 Agent 等待子 Agent,但子 Agent 在等信号量

    父 Agent 持有信号量 → 等待子 Agent → 子 Agent 需要信号量 → 信号量被父 Agent 持有 → 完美闭环。

    三、原因分析:四个隐蔽的多 Agent 死锁陷阱

    陷阱一:父-子 Agent 信号量嵌套死锁

    这是最常见、也最危险的多 Agent 死锁模式。父 Agent 在持有并发槽位的情况下同步等待子 Agent 完成,而子 Agent 需要获取同一个槽位才能启动。

    gptme 的真实代码路径是这样的:

    # 父 Agent 的代码路径
    _sem.acquire() # 父 Agent 获取槽位
    t = Thread(target=run_subagent)
    t.start() # 启动子 Agent 线程
    t.join() # 阻塞等待子线程——槽位仍在父 Agent 手中

    # 子 Agent 的代码路径
    _sem.acquire() # 子 Agent 也尝试获取同一个槽位——永远等不到

    更隐蔽的是,这个死锁在单个任务串行运行时不会触发。只有当父 Agent 和子 Agent 同时运行时才会出现。所以开发环境(一次跑一个任务)永远复现不了,只有生产环境(并发跑多个任务)才会触发。

    陷阱二:跨 Agent 消息队列循环等待

    当多个 Agent 通过消息队列通信,并且彼此等待对方的消息时,就会形成循环等待。

    Camel AI 的异步通信文档明确警告:直接链式调用 step() 会在 Agent 期望双向响应时死锁。

    # 错误:A 等 B 的响应,B 等 A 的响应
    async def agent_a():
    await queue_a.get() # 等 A 队列的消息
    await queue_b.put("A 的消息")
    await queue_a.get() # 再等 A 队列的消息——但 B 还没发

    async def agent_b():
    await queue_a.put("B 的消息")
    await queue_b.get() # 等 B 队列的消息——但 A 还没发

    两个 Agent 都在等对方先发消息,但双方都认为对方应该先发。

    陷阱三:lazy-import 非重入锁自死锁

    PraisonAI 的 issue #3707 记录了一个极其隐蔽的自死锁:lazy-import 使用了非重入的 threading.Lock,但在锁内调用的 getattr() 会再次触发 lazy_import(),同一个线程再次尝试获取同一个锁。

    # _lazy.py 的核心问题
    _cache_lock = threading.Lock() # 非重入锁

    def lazy_import(module_path, attr_name):
    with _cache_lock: # 第一次获取锁
    module = importlib.import_module(module_path)
    value = getattr(module, attr_name) # 这会触发 __getattr__ → 再次调用 lazy_import
    # ↑ 同一个线程再次尝试获取 _cache_lock → 永久阻塞

    复现结果:8 秒超时后进程被 kill,after access 从未打印,进程永久挂起。

    陷阱四:共享状态读-改-写竞态导致的“活锁”

    比死锁更隐蔽的是“活锁”——Agent 没有冻结,但永远在做无用功。

    PraisonAI 的 issue #1365 记录了这个竞态:

    def increment_state(self, key, increment=1, default=0):
    current_value = self.get_state(key, default) # 线程 A 读取 5
    self.set_state(key, current_value + increment) # 线程 A 写入 6
    # 线程 B 也读取 5,写入 6 —— increment 丢失!

    两个 Agent 并发更新同一个状态,每次更新都互相覆盖,看起来一直在跑,但状态永远不推进。

    TradingAgents-CN 的真实故障展示了另一种活锁形态:DeepSeek 模型在新闻分析阶段被反复调用,每次尝试不同工具却始终产出空报告,工作流在条件判断的循环中打转,永远无法进入下一个分析师。根因是工作流条件边只看“有没有 tool_calls”,而不看“任务是否真正完成”。

    一句话总结

    多 Agent 系统的死锁发生在“逻辑协调层”,而不是“资源锁层”。两个 Agent 各自“做对了自己的事”,但组合起来形成了闭环。

    四、修复方案:超时保护 + 循环检测 + 终止谓词

    核心思路

    多 Agent 死锁需要三层防护:

  • 超时保护:所有可能阻塞的操作都必须有超时上限;
  • 循环检测:检测 Agent 间的循环等待关系并主动打破;
  • 终止谓词:每个 Agent 循环必须有可判定的终止条件。
  • 第一层:带超时保护的锁获取

    核心原则:永远不要无限期等待一个锁。所有锁获取都必须有超时。

    import asyncio

    class TimeoutSemaphore:
    """带超时保护的信号量"""

    def __init__(self, value: int, default_timeout: float = 30.0):
    self._sem = asyncio.Semaphore(value)
    self._default_timeout = default_timeout

    async def acquire(self, timeout: float | None = None):
    """获取信号量,超时抛出异常"""
    timeout = timeout or self._default_timeout
    try:
    await asyncio.wait_for(
    self._sem.acquire(),
    timeout=timeout,
    )
    return True
    except asyncio.TimeoutError:
    raise DeadlockDetectedError(
    f"获取信号量超时 ({timeout}s),可能发生死锁"
    )

    def release(self):
    self._sem.release()

    class DeadlockDetectedError(Exception):
    """检测到潜在死锁"""
    pass

    关键设计点:asyncio.wait_for 会在超时后取消 acquire() 的等待,避免协程永久挂起。

    第二层:死锁检测与恢复

    对于父-子 Agent 嵌套场景,需要在等待子 Agent 时临时释放父 Agent 占用的槽位。

    gptme 的修复方案正是这个思路:在父 Agent 阻塞等待子线程之前,用 temporarily_release_current_slot() 释放当前槽位,等待完成后再重新获取。

    import asyncio
    from contextlib import asynccontextmanager

    class DeadlockAwareOrchestrator:
    """死锁感知的 Agent 编排器"""

    def __init__(self, max_concurrent: int = 5):
    self._sem = TimeoutSemaphore(max_concurrent)
    self._held_slots: set[str] = set()

    @asynccontextmanager
    async def agent_slot(self, agent_id: str):
    """Agent 槽位上下文管理器"""
    await self._sem.acquire()
    self._held_slots.add(agent_id)
    try:
    yield
    finally:
    self._held_slots.discard(agent_id)
    self._sem.release()

    async def run_subagent_safely(
    self, parent_id: str, subagent_coro,
    ):
    """安全地运行子 Agent:临时释放父 Agent 的槽位"""
    # 临时释放父 Agent 的槽位
    parent_holds_slot = parent_id in self._held_slots
    if parent_holds_slot:
    self._sem.release()
    self._held_slots.discard(parent_id)
    print(f"[ORCH] 父 Agent {parent_id} 临时释放槽位")

    try:
    # 给子 Agent 留出获取槽位的机会
    await asyncio.sleep(0)
    result = await asyncio.wait_for(
    subagent_coro,
    timeout=60.0,
    )
    return result
    except asyncio.TimeoutError:
    raise DeadlockDetectedError(
    f"子 Agent 执行超时,父 Agent: {parent_id}"
    )
    finally:
    # 重新获取父 Agent 的槽位
    if parent_holds_slot:
    await self._sem.acquire()
    self._held_slots.add(parent_id)
    print(f"[ORCH] 父 Agent {parent_id} 重新获取槽位")

    循环等待检测:维护一个“谁在等谁”的依赖图,检测环:

    class WaitForGraph:
    """等待关系图:检测循环等待"""

    def __init__(self):
    self._edges: dict[str, set[str]] = {}

    def add_wait(self, waiter: str, target: str):
    """记录 waiter 正在等待 target"""
    self._edges.setdefault(waiter, set()).add(target)

    def remove_wait(self, waiter: str, target: str):
    if waiter in self._edges:
    self._edges[waiter].discard(target)

    def detect_cycle(self) –> list[str] | None:
    """检测循环等待,返回环上的节点列表"""
    WHITE, GRAY, BLACK = 0, 1, 2
    color: dict[str, int] = {}

    def dfs(node: str) –> list[str] | None:
    color[node] = GRAY
    for neighbor in self._edges.get(node, set()):
    if color.get(neighbor) == GRAY:
    # 发现环
    return [node, neighbor]
    if color.get(neighbor, WHITE) == WHITE:
    result = dfs(neighbor)
    if result:
    result.insert(0, node)
    return result
    color[node] = BLACK
    return None

    for node in list(self._edges.keys()):
    if color.get(node, WHITE) == WHITE:
    cycle = dfs(node)
    if cycle:
    return cycle
    return None

    第三层:循环终止谓词

    每个 Agent 循环必须有可判定的终止条件。不能依赖 LLM 自己判断“我完成了”。

    class AgentLoopGuard:
    """Agent 循环守卫:确保循环一定终止"""

    def __init__(
    self,
    max_iterations: int = 20,
    max_no_progress: int = 3,
    ):
    self._max_iterations = max_iterations
    self._max_no_progress = max_no_progress
    self._iteration = 0
    self._no_progress_count = 0
    self._last_output_hash: str | None = None

    def should_continue(self, current_output: str) –> bool:
    """判断循环是否应该继续"""
    self._iteration += 1

    # 条件 1:最大迭代次数
    if self._iteration > self._max_iterations:
    print(f"[GUARD] 达到最大迭代次数 {self._max_iterations}")
    return False

    # 条件 2:连续无进展
    output_hash = hash(current_output)
    if output_hash == self._last_output_hash:
    self._no_progress_count += 1
    if self._no_progress_count >= self._max_no_progress:
    print(f"[GUARD] 连续 {self._max_no_progress} 轮无进展")
    return False
    else:
    self._no_progress_count = 0
    self._last_output_hash = output_hash

    return True

    五、Agent 开发中的特殊场景

    场景一:LangGraph 工作流的循环失控

    TradingAgents-CN 的修复方案采用“清洁消息 + 计数器 + 报告就绪判断”三层防线:

    def should_continue_news(self, state: AgentState) –> str:
    """增强版条件判断:不再只看 tool_calls"""
    messages = state["messages"]
    last_message = messages[–1]

    # 条件 1:检查是否真的产出了非空报告
    report = state.get("news_report", "")
    if len(report.strip()) > 0:
    return "Msg Clear News" # 有报告,进入下一分析师

    # 条件 2:检查迭代次数
    iteration = state.get("iteration_count", 0)
    if iteration > 10:
    return "Msg Clear News" # 超过上限,强制退出

    # 条件 3:检查是否有 tool_calls
    if hasattr(last_message, 'tool_calls') and last_message.tool_calls:
    return "tools_news"

    return "Msg Clear News"

    场景二:AutoGen GraphFlow 状态损坏的恢复

    当工作流在 Agent 切换期间被中断时,保存的状态可能损坏。AutoGen 的修复思路是在保存状态时做原子性检查:

    class AtomicWorkflowState:
    """原子性工作流状态"""

    def __init__(self):
    self._state = {}
    self._version = 0
    self._lock = threading.Lock()

    def save_transition(self, from_agent: str, to_agent: str):
    """原子地保存 Agent 切换状态"""
    with self._lock:
    new_state = {
    **self._state,
    "current_agent": to_agent,
    "previous_agent": from_agent,
    "version": self._version + 1,
    }
    # 先写入临时状态,确认后再替换
    self._state = new_state
    self._version += 1

    def is_valid(self) –> bool:
    """验证状态一致性"""
    ready = self._state.get("ready", [])
    remaining = self._state.get("remaining", {})
    if remaining and not ready:
    return False # 还有工作但没有就绪的 Agent
    return True

    场景三:多 Agent 系统的并发控制清单

    设计原则说明实现方式
    锁获取必须有超时 永远不无限等待 asyncio.wait_for
    父-子 Agent 不嵌套持锁 等待子任务时释放父槽位 temporarily_release
    循环必须有终止谓词 不依赖 LLM 判断完成 迭代计数 + 无进展检测
    状态更新必须原子 读-改-写用锁保护 asyncio.Lock
    依赖图必须无环 定期检测循环等待 WaitForGraph.detect_cycle()

    踩坑检查清单

    检查项危险信号修复建议
    父-子 Agent 嵌套 父持有槽位等待子 Agent 等待时临时释放槽位
    锁获取 await lock.acquire() 无超时 加 asyncio.wait_for
    lazy-import 锁 threading.Lock 非重入 换 threading.RLock
    循环终止 依赖 LLM 判断“我完成了” 加迭代计数 + 无进展检测
    状态更新 裸 self.state[key] = value 加锁保护读-改-写
    消息队列 双向等待无超时 加 wait_for + 循环检测
    工作流恢复 中断后状态不可恢复 状态保存做原子性检查

    六、排查技巧:如何发现多 Agent 死锁

    方法一:用 faulthandler 转储所有线程栈

    import faulthandler
    import signal

    # 注册信号处理器:收到 SIGUSR1 时转储所有线程栈
    faulthandler.register(signal.SIGUSR1)

    # 或者在超时后自动转储
    faulthandler.dump_traceback_later(60, exit=True)

    当进程卡住时,发送 SIGUSR1 即可看到所有线程的调用栈,快速定位死锁位置。

    方法二:用 asyncio debug 模式检测阻塞

    asyncio.run(main(), debug=True)

    debug 模式会检测“慢回调”和“未 await 的协程”。

    方法三:监控等待关系图

    class WaitForMonitor:
    """监控 Agent 间的等待关系"""

    def __init__(self):
    self._graph = WaitForGraph()

    def report(self) –> str:
    cycle = self._graph.detect_cycle()
    if cycle:
    return f"[DEADLOCK] 检测到循环等待: {' → '.join(cycle)}"
    return f"[OK] 当前无循环等待,活跃等待边: {len(self._graph._edges)}"

    方法四:统计每个 Agent 的阻塞时间

    import time

    class AgentBlocker:
    """统计 Agent 阻塞时间"""

    def __init__(self):
    self._block_start: dict[str, float] = {}

    def start_wait(self, agent_id: str):
    self._block_start[agent_id] = time.monotonic()

    def end_wait(self, agent_id: str) –> float:
    start = self._block_start.pop(agent_id, None)
    if start is None:
    return 0.0
    elapsed = time.monotonic() – start
    if elapsed > 5.0:
    print(f"[WARN] Agent {agent_id} 阻塞了 {elapsed:.1f}s")
    return elapsed

    如果某个 Agent 的阻塞时间超过阈值,说明可能存在死锁或性能瓶颈。

    七、总结

    这次事故让我记住了一句话:

    多 Agent 系统的死锁发生在“逻辑协调层”,而不是“资源锁层”。两个 Agent 各自“做对了自己的事”,但组合起来形成了闭环。

    核心要点回顾:

  • 父-子 Agent 嵌套时,父 Agent 持有并发槽位等待子 Agent,子 Agent 需要同一个槽位,形成闭环死锁;
  • 所有锁获取必须有超时保护,asyncio.wait_for 是基础工具;
  • 父 Agent 等待子任务时应临时释放槽位,gptme 的 temporarily_release_current_slot 是正确的模式;
  • lazy-import 必须用可重入锁(threading.RLock),否则同线程重入会自死锁;
  • 每个 Agent 循环必须有可判定的终止谓词:迭代计数 + 无进展检测 + 状态就绪判断;
  • 共享状态更新必须原子化,读-改-写要用锁保护,否则会出现“活锁”;
  • 用 faulthandler + 等待关系图快速定位死锁位置。
  • 写 Python Agent 时,多 Agent 协作不是“多写几个 Agent 就行”,而是需要一套完整的并发控制设计。从项目第一天就给所有锁加超时,给所有循环加终止谓词,给所有状态更新加原子保护。否则,你的多 Agent 系统会在某个深夜悄悄冻结,直到第二天早上你发现几十个任务全部卡在“运行中”。

    #mermaid-svg-hvXxxrY1ntp4DwGs{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hvXxxrY1ntp4DwGs .error-icon{fill:#552222;}#mermaid-svg-hvXxxrY1ntp4DwGs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hvXxxrY1ntp4DwGs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hvXxxrY1ntp4DwGs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hvXxxrY1ntp4DwGs .marker.cross{stroke:#333333;}#mermaid-svg-hvXxxrY1ntp4DwGs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hvXxxrY1ntp4DwGs p{margin:0;}#mermaid-svg-hvXxxrY1ntp4DwGs .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster-label text{fill:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster-label span{color:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster-label span p{background-color:transparent;}#mermaid-svg-hvXxxrY1ntp4DwGs .label text,#mermaid-svg-hvXxxrY1ntp4DwGs span{fill:#333;color:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs .node rect,#mermaid-svg-hvXxxrY1ntp4DwGs .node circle,#mermaid-svg-hvXxxrY1ntp4DwGs .node ellipse,#mermaid-svg-hvXxxrY1ntp4DwGs .node polygon,#mermaid-svg-hvXxxrY1ntp4DwGs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hvXxxrY1ntp4DwGs .rough-node .label text,#mermaid-svg-hvXxxrY1ntp4DwGs .node .label text,#mermaid-svg-hvXxxrY1ntp4DwGs .image-shape .label,#mermaid-svg-hvXxxrY1ntp4DwGs .icon-shape .label{text-anchor:middle;}#mermaid-svg-hvXxxrY1ntp4DwGs .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hvXxxrY1ntp4DwGs .rough-node .label,#mermaid-svg-hvXxxrY1ntp4DwGs .node .label,#mermaid-svg-hvXxxrY1ntp4DwGs .image-shape .label,#mermaid-svg-hvXxxrY1ntp4DwGs .icon-shape .label{text-align:center;}#mermaid-svg-hvXxxrY1ntp4DwGs .node.clickable{cursor:pointer;}#mermaid-svg-hvXxxrY1ntp4DwGs .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hvXxxrY1ntp4DwGs .arrowheadPath{fill:#333333;}#mermaid-svg-hvXxxrY1ntp4DwGs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hvXxxrY1ntp4DwGs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hvXxxrY1ntp4DwGs .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hvXxxrY1ntp4DwGs .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hvXxxrY1ntp4DwGs .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hvXxxrY1ntp4DwGs .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster text{fill:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs .cluster span{color:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hvXxxrY1ntp4DwGs .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hvXxxrY1ntp4DwGs rect.text{fill:none;stroke-width:0;}#mermaid-svg-hvXxxrY1ntp4DwGs .icon-shape,#mermaid-svg-hvXxxrY1ntp4DwGs .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hvXxxrY1ntp4DwGs .icon-shape p,#mermaid-svg-hvXxxrY1ntp4DwGs .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hvXxxrY1ntp4DwGs .icon-shape .label rect,#mermaid-svg-hvXxxrY1ntp4DwGs .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hvXxxrY1ntp4DwGs .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hvXxxrY1ntp4DwGs .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hvXxxrY1ntp4DwGs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    等待子 Agent

    等待槽位

    无法释放

    父 Agent持有槽位 Semaphore=1

    子 Agent需要槽位才能启动

    槽位被父 Agent 持有

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Python Agent 踩坑实录:我的多 Agent 系统在深夜互相等死
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!