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 叠加形成的完美死锁:
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 死锁需要三层防护:
第一层:带超时保护的锁获取
核心原则:永远不要无限期等待一个锁。所有锁获取都必须有超时。
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 各自“做对了自己的事”,但组合起来形成了闭环。
核心要点回顾:
写 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 持有
网硕互联帮助中心








评论前必须登录!
注册