一句话

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

因为无环 + 有向,天然对应「可调度的依赖关系」:

  1. 找出当前没有未完成依赖的节点(入度为 0,或依赖都已完成)
  2. 执行它们(可以串行,也可以在无相互依赖时并行)
  3. 完成后解锁下游节点
  4. 重复直到全部完成

把节点排成一个「不违背任何依赖边」的线性顺序,叫拓扑排序(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」这类说法时,指的就是:步骤是节点,依赖是有向边,整张图无环,因此可以安全地按依赖解锁推进。