一句话
DAG = Directed Acyclic Graph,中文常叫有向无环图。它是一种图:边有方向,且沿着方向走永远走不回起点。
先说「图」
图(Graph)由两类东西组成:
- 节点(Node / Vertex):事物本身,比如「编译模块 A」「跑测试」「部署」
- 边(Edge):节点之间的关系,比如「A 完成后才能做 B」
边如果没有方向(A—B 和 B—A 一样),叫无向图;边有方向(A → B 表示从 A 到 B),叫有向图(Directed Graph)。
Directed:边有方向
在任务依赖、数据流水线里,方向通常表示「先后」或「依赖」:
写接口 ──► 写单测 ──► 跑 pytest
意思是:写接口完成之后才能写单测;单测就绪之后才能跑 pytest。反过来走没有意义——不能「先跑 pytest 再写接口」。
Acyclic:不能有环
「无环」是说:沿着箭头走,不会绕回已经走过的节点。
有环的例子(这不是 DAG):
A ──► B
▲ │
└─────┘
A 等 B,B 又等 A,两边永远等不到对方。包管理里的循环依赖、构建系统里的互相引用,本质都是环带来的死锁。
无环的例子(这是 DAG):
┌──► B ──► D
A ───┤
└──► C ──► E
从 A 出发可以走到 B、C、D、E,但没有任何一条路径能回到 A。因此一定存在至少一个「没有入边的起点」,以及至少一个「没有出边的终点」。
为什么工程里爱用 DAG
因为无环 + 有向,天然对应「可调度的依赖关系」:
- 找出当前没有未完成依赖的节点(入度为 0,或依赖都已完成)
- 执行它们(可以串行,也可以在无相互依赖时并行)
- 完成后解锁下游节点
- 重复直到全部完成
把节点排成一个「不违背任何依赖边」的线性顺序,叫拓扑排序(Topological Sort)。DAG 一定能拓扑排序;有环的有向图则不能(至少对成环的那部分不行)。
这也是为什么 Airflow、Makefile、包管理器、CI pipeline、以及 Graph Engineering 里用任务队列当图 时,都爱用 DAG 这个词:它们要的不是随便一张网,而是「有先后、无死锁」的调度结构。
常见出现场景
| 场景 | 节点大致是什么 | 边大致表示什么 |
|---|---|---|
| Makefile / 构建 | 目标文件、产物 | 依赖哪些输入 |
| 包管理 | 库 / 模块 | 依赖关系 |
| Airflow / 数据管道 | 一个 Task | 上游 Task 完成后才能跑 |
| CI pipeline | job / stage | 阶段依赖 |
| Agent 任务编排 | 一个专职步骤 | 阻塞 / 被阻塞 |
注意:很多「图编排」框架(例如 LangGraph)在运行时允许带环——比如失败后回到修复节点再试。那是状态机 / 有向图,不一定再强调 Acyclic。叫 DAG 时,通常已经假定调度结构本身无环;重试、循环往往落在「某一个节点内部」或「边上的条件回到上游」,实现细节因框架而异。
和「树」差在哪
树也是无环的,但更严:通常有唯一根,且任意两个节点之间至多一条路径。
DAG 更松:可以有多个「源头」,也可以多个上游汇到同一个下游(如 B 和 C 都完成后才能做 D)。任务编排里这种「多依赖汇合」很常见,所以用 DAG 而不是树。
树:每个节点(根除外)只有一个父
DAG:一个节点可以有多个前驱,也可以有多个后继
小结
记三个词就够:
- Directed:关系有方向(先后 / 依赖)
- Acyclic:不能绕圈(否则调度会死锁)
- Graph:用节点和边把整张依赖关系画清楚,再按拓扑顺序执行
碰到「任务队列当 DAG」「pipeline 是一张 DAG」这类说法时,指的就是:步骤是节点,依赖是有向边,整张图无环,因此可以安全地按依赖解锁推进。