385 lines
38 KiB
Markdown
385 lines
38 KiB
Markdown
# 12 门语言的执行模型与生命周期
|
||
|
||
本文比较代码如何进入执行、运行状态如何保存、等待后如何继续,以及资源如何结束存续。逐词含义见[关键词手册](language-keywords.md);请求、场景和界面的完整路径见[框架如何接管代码执行](framework-runtime-lifecycles.md)。本文是外部语言参考,不定义 Endorphin 的新语法或默认调度规则。
|
||
|
||
执行模型资料核对日期:2026-10-13。关键词版本基线沿用原手册;这里分析指定实现的机制,不宣称覆盖所有实现或版本。Python 使用 CPython 3.14;Java 使用 Java SE 26 的规范;C 使用 C23 公开草案;C++ 使用公开工作草案的相关条款,不将其所有新增能力归入 C++23。Go runtime、React 源码和其他持续更新的官方资料,只作为所注明实现的证据。Kotlin 已发布规范中的协程说明用于解释机制,不作为 Kotlin 2.4 全量版本审计;Julia 使用官方 v1 文档,不将实现细节冻结为 1.11 的全部承诺。
|
||
|
||
## 阅读路径
|
||
|
||
先阅读统一模板,再选一门熟悉的语言。建立普通调用的认识后,比较协程和任务,最后进入框架案例。
|
||
|
||
| 语言 | 分析入口 | 框架或宿主案例 |
|
||
| --- | --- | --- |
|
||
| Python | [CPython、执行帧与协程](#1-python) | [FastAPI](framework-runtime-lifecycles.md#路径二fastapi) |
|
||
| JavaScript | [执行上下文、Promise 与宿主](#2-javascript) | [Node/Chromium](framework-runtime-lifecycles.md#node-与-chromium)、[React](framework-runtime-lifecycles.md#路径五react) |
|
||
| TypeScript | [类型处理与 JavaScript 执行](#3-typescript) | [TSX 与 React](framework-runtime-lifecycles.md#路径五react) |
|
||
| Java | [JVM、类初始化与线程](#4-java) | [Spring Boot](framework-runtime-lifecycles.md#路径三spring-boot) |
|
||
| C | [进程入口与存储期](#5-c) | [普通进程](framework-runtime-lifecycles.md#路径一c-普通进程) |
|
||
| C++ | [对象生命周期与协程帧](#6-c) | 库或宿主提供调度 |
|
||
| C# | [.NET、Task 与状态机](#7-c) | [Unity](framework-runtime-lifecycles.md#路径四unity) |
|
||
| Go | [goroutine、channel 与 runtime](#8-go) | runtime 调度 |
|
||
| Rust | [所有权、Future 与 executor](#9-rust) | 异步运行库提供调度 |
|
||
| Swift | [ARC、Task 与 executor](#10-swift) | 并发运行时及应用宿主 |
|
||
| Kotlin | [Continuation、Job 与 dispatcher](#11-kotlin) | 协程库及目标平台 |
|
||
| Julia | [方法、Task 与调度](#12-julia) | Julia 运行时 |
|
||
|
||
## 统一分析模板
|
||
|
||
### 常用结构名称
|
||
|
||
| 名称 | 本文中的含义 |
|
||
| --- | --- |
|
||
| frame/执行帧 | 一次调用或执行的参数、局部状态与继续关系;具体布局由实现决定 |
|
||
| continuation/继续关系 | 暂停后该怎样继续计算,可以用保存状态、函数或其他结构表达 |
|
||
| 状态机 | 将执行进度表示为不同状态及迁移,并保存跨暂停需要的数据 |
|
||
| scheduler/executor | 接收工作并安排推进的设施;不等于所有计算都由它解释执行 |
|
||
| awaiter/等待协议 | 检查是否完成,并在需要等待时建立继续关系的机制 |
|
||
| Task/Job | 具体运行环境管理的活动或工作;同名设施在不同语言中并不等价 |
|
||
| Future/Promise | 表达结果及其后续取得或推进协议的设施;不等于专属线程 |
|
||
| GC/ARC/RAII | 分别强调可达性回收、引用计数管理、将资源管理关联到对象初始化与析构;具体保证见各语言 |
|
||
| JIT/AOT | 分别表示运行时编译与提前编译;它们回答代码怎样准备,不直接定义哪个业务事件触发调用 |
|
||
|
||
### 生命周期属于一个具体实体
|
||
|
||
“程序的生命周期”通常包含多个不同尺度的生命周期。进程仍然存在时,函数调用可以已经返回;组件仍然存在时,某次渲染可以已经完成;任务暂停时,其上下文仍然存在。分析时先明确实体,再讨论状态。
|
||
|
||
| 实体 | 典型开始 | 典型结束 | 不能混淆的关系 |
|
||
| --- | --- | --- | --- |
|
||
| 进程或应用 | 启动入口被接管 | 正常退出、关闭或终止 | 应用存续不意味着用户代码持续执行 |
|
||
| REPL 会话 | 创建输入与求值上下文 | 会话关闭 | 一次输入完成后,绑定仍可保留 |
|
||
| 函数定义 | 定义被创建、加载或准备 | 不再需要或所属环境结束 | 定义存在不意味着函数体已经执行 |
|
||
| 一次调用 | 控制流进入函数 | 返回或异常离开 | 同一函数可以对应很多次独立调用 |
|
||
| 协程或任务 | 创建状态及进入执行 | 完成、失败或取消处理结束 | 创建、调度、首次运行可能是不同动作 |
|
||
| 组件或订阅 | 挂载、启用或注册 | 卸载、禁用或取消订阅 | 一次回调返回通常不销毁组件或订阅 |
|
||
| 数据对象或资源 | 构造、分配或获得资源 | 释放、销毁或回收 | 内存回收与文件、连接关闭分别处理 |
|
||
|
||
### 定义、上下文、管理结构分别保存信息
|
||
|
||
下面是比较用的概念分类,不要求每个实现都存在三个同名结构。
|
||
|
||
| 结构组 | 保存的信息 | 常见实例 |
|
||
| --- | --- | --- |
|
||
| 代码与定义 | 可执行逻辑、类型、模块、依赖及捕获环境 | code object、函数对象、方法元数据、机器码、模块记录 |
|
||
| 一次执行的上下文 | 输入、局部绑定、进度、等待及结果关系 | 栈帧、协程帧、Continuation、goroutine 栈 |
|
||
| 调度与管理结构 | 就绪工作、等待者、触发关联及所有者 | 就绪队列、I/O 注册表、Task、Job、组件树、订阅表 |
|
||
|
||
执行上下文需要能够支持下列职责,但信息可以分散保存、被编译器消除,或由外部宿主提供。
|
||
|
||
| 信息 | 作用 | 典型承载方式 |
|
||
| --- | --- | --- |
|
||
| 本次采用的代码与依赖 | 确定执行哪份逻辑 | 方法元数据、函数引用、代码地址与环境 |
|
||
| 输入与局部绑定 | 保存这次计算的数据 | 局部槽位、寄存器、栈、状态机字段 |
|
||
| 当前进度 | 决定继续执行的位置 | 指令位置、返回地址、状态机标签 |
|
||
| 执行状态 | 区分运行、等待和结束 | Task/Job 状态、调度器记录 |
|
||
| 等待条件与继续位置 | 外部条件满足后能够继续 | awaiter、Waker、等待队列、Continuation |
|
||
| 调用关系与结果接收者 | 交付返回值或失败 | 调用栈、Promise reaction、父任务、回调 |
|
||
| 执行身份与可用能力 | 判断操作的归属与授权 | 进程权限、请求上下文、宿主能力;不是所有语言执行帧的原生字段 |
|
||
|
||
对象地址只在所在进程的地址空间里有意义。网络发送的是协议表示;接收端创建或更新自己的对象。跨端关联必须另有身份和通信约定,不能从局部变量名字推断远端内存位置。Endorphin 对平台对象的说明见[资源访问与呈现](../guide/11-resource-access-and-presentation.md#117-跨端对象与同步)。
|
||
|
||
### 触发、调度与执行承担不同职责
|
||
|
||
触发关系确定发生某个条件后应进入哪段逻辑。调度确定工作何时获得执行机会。执行引擎或原生代码推进具体计算。普通同步调用可以直接进入函数,不经过应用任务队列;应用事件循环之外,还可能有操作系统线程调度。
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
A[启动、输入、事件或条件满足] --> B[确定入口与输入]
|
||
B --> C[直接调用或提交可运行工作]
|
||
C --> D[执行引擎或原生代码推进]
|
||
D --> E{本次执行的结果}
|
||
E --> F[返回或失败:交付结果并清理]
|
||
E --> G[等待:保存上下文与等待关系]
|
||
G --> H[条件满足:重新获得执行机会]
|
||
H --> D
|
||
```
|
||
|
||
图中是概念路径,不能据此推出统一队列、抢占策略或状态枚举。回调注册通常保存关联,注册函数自身也可能执行初始化逻辑;必须查具体 API。等待结果的任务也不一定需要不断主动轮询。
|
||
|
||
比较可暂停活动时,可以使用下面的分析状态;它们不是各语言共有的原生枚举。
|
||
|
||
| 分析状态 | 进入条件 | 后续处理 |
|
||
| --- | --- | --- |
|
||
| 已创建 | 逻辑与初始上下文已经关联 | 可能尚未调度或运行,例如新建的 Julia Task |
|
||
| 可运行 | 有条件推进,但尚未获得执行机会 | 由相应调度策略选中 |
|
||
| 运行中 | 正在推进代码 | 继续、等待、完成或失败 |
|
||
| 等待中 | 当前不能继续,已保存状态及等待关系 | 条件满足后获得继续机会 |
|
||
| 已完成/失败 | 逻辑得到结果或以错误结束 | 交付结果,完成相应清理;结果句柄仍可存续 |
|
||
|
||
取消请求可以在运行或等待期间出现;活动接受取消、执行清理并终止之前,不能把“请求取消”直接当成“已结束”。组件的启用、提交或卸载也不是上表的同一套状态。
|
||
|
||
### 恢复、重算、释放各有含义
|
||
|
||
恢复是在保留的上下文中继续;重算是开始另一次求值。React 再次调用组件属于重新计算,Rust executor 再次 poll 同一个 Future 属于推进保存的状态。取消通常是请求停止及处理清理,不自动撤销已经发生的写入。GC 判断内存对象是否可回收,不代替所有业务资源的关闭。
|
||
|
||
## 1. Python
|
||
|
||
**入口与执行者。** CPython 执行脚本、模块或 REPL 输入中的代码块。模块顶层会执行;执行 `def` 创建函数并处理默认值、装饰器等,普通函数体在调用时运行。`class` 的类体在定义类时执行。首次导入通常执行模块初始化,后续导入通常使用模块缓存。Python 规范中的执行帧概念与 CPython 具体实现应分开。[Python 执行模型](https://docs.python.org/3.14/reference/executionmodel.html)、[复合语句](https://docs.python.org/3.14/reference/compound_stmts.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| code object | 编译后的代码及常量、名称等元数据 | 可被函数引用和多次执行;不包含某次调用的全部数据 |
|
||
| function object | code、全局环境、默认值、闭包等 | 定义执行时创建,通常跨多次调用存续 |
|
||
| 执行帧及解释器内部状态 | 局部绑定、进度、调用关系 | 一次执行;可因生成器、协程或外部引用保留 |
|
||
| coroutine/generator、asyncio Task | 可暂停状态;Task 进一步跟踪结果、取消和调度 | 调用者或事件循环持有,完成不等于引用立即消失 |
|
||
| PyObject 及具体对象结构 | CPython 对象的类型及具体内容 | 对象存续期间;不是“当前正在执行”的标记 |
|
||
|
||
`PyObject` 是 CPython 对象表示的基础,不是在每次运行字节码时才统一创建。启动初始化、执行定义、构造值都可能产生对象;code、function 与一次执行的帧也不同。[对象 C API 说明](https://docs.python.org/3.14/c-api/intro.html#objects-types-and-reference-counts)、[函数对象 C API](https://docs.python.org/3.14/c-api/function.html)、[代码对象 C API](https://docs.python.org/3.14/c-api/code.html)
|
||
|
||
具体帧内部表示不等于每次调用立即创建一个可见 `PyFrameObject`。CPython 的帧优化允许在需要时才物化相应 Python 帧对象;不能用调试器看见的对象反推每次调用的全部分配行为。[按需创建 Python 帧](https://docs.python.org/3.14/whatsnew/3.11.html#cheaper-lazy-python-frames)
|
||
|
||
**进入与等待。** 普通函数调用立即进入函数体;生成器调用先取得生成器,迭代推进它。调用 `async def` 先得到 coroutine,随后由 `await`、`asyncio.run` 或 Task 等方式驱动。Task 的急切执行配置会改变首次推进时机,不能一律理解为下一轮循环才开始。[asyncio 协程与任务](https://docs.python.org/3.14/library/asyncio-task.html)
|
||
|
||
下面是 CPython/asyncio 的独立示例,预期输出顺序为 `created`、`body`、`42`,不是运行日志。
|
||
|
||
```python
|
||
import asyncio
|
||
|
||
async def price():
|
||
print("body")
|
||
await asyncio.sleep(0)
|
||
return 42
|
||
|
||
async def main():
|
||
pending = price()
|
||
print("created")
|
||
print(await pending)
|
||
|
||
asyncio.run(main())
|
||
```
|
||
|
||
**结束与关键词。** `return` 和异常结束调用;`yield` 保存生成器继续状态,`await` 表达等待。`with`、`finally` 用于资源退出处理;`del` 删除绑定等,不保证对象立即销毁。CPython 的引用计数及循环 GC 是实现机制,不是所有 Python 实现必须照搬的机制。[Python 数据模型](https://docs.python.org/3.14/reference/datamodel.html)。请求入口另由 [FastAPI 与 ASGI](framework-runtime-lifecycles.md#路径二fastapi)组织。
|
||
|
||
## 2. JavaScript
|
||
|
||
**入口与执行者。** 宿主启动脚本或模块求值,再通过普通调用和回调进入函数。V8 是 Node 与 Chromium 使用的执行引擎;网络、DOM、定时器和完整宿主事件循环分别由宿主提供。ECMAScript 定义执行上下文与环境记录,它们是规范模型,不要求 V8 使用相同名称或布局。[ECMAScript 执行上下文](https://tc39.es/ecma262/2026/multipage/executable-code-and-execution-contexts.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 函数、脚本/模块及环境 | 逻辑、绑定和闭包捕获 | 引擎管理;模块求值与函数调用分别发生 |
|
||
| 执行上下文栈 | 当前函数、环境及执行进度 | 活跃的同步调用;闭包环境可继续存续 |
|
||
| Promise 与 reaction | 完成状态、结果及后续处理 | Promise 解决后仍可被引用,不等于线程或整个执行帧 |
|
||
| 生成器/异步继续状态 | 暂停后的局部信息与继续关系 | 引擎保存并按相应协议恢复 |
|
||
| 宿主任务与微任务管理 | 可运行的回调或 job | 队列、检查点和执行时机受宿主规范约束 |
|
||
|
||
**进入与等待。** 普通函数立即运行;`async` 函数调用会运行同步前缀,再返回 Promise,`await` 后续通过异步继续机制推进;生成器由 `next()` 等驱动。Promise 构造器的 executor 与 `.then()` 注册的后续处理不是同一个时机。[ECMAScript 函数定义](https://tc39.es/ecma262/2026/multipage/ecmascript-language-functions-and-classes.html)、[Promise 对象](https://tc39.es/ecma262/2026/multipage/control-abstraction-objects.html#sec-promise-objects)
|
||
|
||
下面是普通脚本示例,预期输出 `start`、`outside`、`resume`。
|
||
|
||
```javascript
|
||
async function price() {
|
||
console.log("start");
|
||
await Promise.resolve();
|
||
console.log("resume");
|
||
}
|
||
price();
|
||
console.log("outside");
|
||
```
|
||
|
||
**调度与结束。** 浏览器在规定位置执行微任务检查点,清空过程中还可加入新微任务;渲染发生在符合条件的渲染机会,不保证每个任务后立即绘制。Node 另有 libuv 阶段和 `process.nextTick` 规则。`return`、`throw`、`try/finally` 描述调用及清理,GC 不能代替解除事件监听或业务资源关闭。[HTML 事件循环](https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model)、[Node 事件循环](https://nodejs.org/learn/asynchronous-work/event-loop-timers-and-nexttick)
|
||
|
||
## 3. TypeScript
|
||
|
||
**入口与执行者。** 通常由工具处理 TypeScript,输出或交给后续工具产生 JavaScript,再由所选宿主执行。类型检查不是持续坐在 JavaScript 和 V8 之间的另一台解释器。`type`、`interface` 等类型信息通常被擦除;部分构造如普通 `enum`、`namespace` 可以生成运行时代码,不能把所有 TS 内容都理解为无运行效果。[类型擦除](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#erased-types)、[枚举](https://www.typescriptlang.org/docs/handbook/enums.html)、[namespace](https://www.typescriptlang.org/docs/handbook/namespaces.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 编译工具的 AST、符号与类型信息 | 源码关系、类型检查及转换所需信息 | 构建或编辑会话;通常不进入应用运行堆 |
|
||
| 生成的 JS 模块、函数和对象 | 真正运行的逻辑与数据 | 采用 JavaScript 的运行模型 |
|
||
| source map | 源码与生成代码位置的对应 | 调试工具使用;不负责调度 |
|
||
| 框架自己的组件、任务或请求记录 | React、服务端等管理状态 | 由对应框架承担,不是 TypeScript 类型对象 |
|
||
|
||
**调用与等待。** `async`、`await` 的可观察调用行为依照目标 JavaScript 及工具生成的支持代码。TS 的返回类型 `Promise<number>` 描述结果契约,不因类型标注自行启动任务。值导入可能带来模块初始化;纯类型导入不为类型本身创建业务运行对象。
|
||
|
||
**TSX 与结束。** JSX 可以由 TypeScript 转换,也可以保留给其他工具处理;生成代码调用 React 等库,然后才进入组件管理。清理、事件循环和对象回收归实际宿主与库。完整路径见 [React 构建、浏览器执行及 SSR](framework-runtime-lifecycles.md#路径五react)。[TypeScript JSX 模式](https://www.typescriptlang.org/docs/handbook/jsx.html)
|
||
|
||
## 4. Java
|
||
|
||
**入口与执行者。** 启动器选择入口,JVM 完成必要的类加载、链接、初始化并调用入口方法。示例可采用传统 `static main(String[])`,但不能把 Java SE 26 的所有入口都限定为这个签名。类初始化与实例构造也不同;`import` 是名称使用规则,不是每次运行到一行就远程加载并执行一个类。[Java 启动与初始化](https://docs.oracle.com/javase/specs/jls/se26/html/jls-12.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 类元数据、方法及运行时常量池 | 已加载定义与方法逻辑 | JVM/类加载器管理 |
|
||
| 每线程的 JVM 栈与 frame | 局部变量、操作数栈及调用关联 | 一次方法调用建立 frame,正常或异常离开时结束 |
|
||
| 堆中的实例 | 字段与对象关系 | 可越过调用边界,由 GC 管理可达性 |
|
||
| Thread、执行器及 Future 类设施 | 线程活动、待提交工作和结果 | 标准库及宿主策略;不要求统一事件循环 |
|
||
|
||
JVM 规范区分共享堆与每线程状态,但不规定所有实现的 GC、JIT 或物理内存布局。解释执行、编译后的机器码执行都可实现相同方法语义。[JVM 运行时数据区与 frame](https://docs.oracle.com/javase/specs/jvms/se26/html/jvms-2.html)
|
||
|
||
**开始与等待。** 普通方法调用直接进入;`Thread.start()` 启动线程执行其逻辑,而直接调用 `run()` 仍是普通方法调用。平台线程、虚拟线程、执行器及异步结果设施各有管理契约。阻塞当前线程不是“Java 必须有一个浏览器式微任务队列”。[Thread API](https://docs.oracle.com/en/java/javase/26/docs/api/java.base/java/lang/Thread.html)
|
||
|
||
**结束与关键词。** `return`、`throw` 结束调用,`finally` 和带资源的 `try` 处理退出;对象可达性与外部资源关闭分别判断。`synchronized` 影响互斥和内存可见性,不负责启动应用请求。Spring 中的 bean 和请求生命周期见 [Spring Boot 案例](framework-runtime-lifecycles.md#路径三spring-boot)。
|
||
|
||
## 5. C
|
||
|
||
**入口与执行者。** 在 hosted 环境中,启动流程完成必要准备后进入 `main`;freestanding 环境的启动由实现规定。常见原生实现将函数编译为机器码,由操作系统启动进程、调度线程,CPU 执行指令。C 规范不要求应用事件循环,也不规定一定有某种物理栈布局。[C23 公开草案 N3096,5.1.2、6.2.4](https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3096.pdf)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 可执行代码、全局及静态对象 | 函数逻辑和长期状态 | 加载与启动流程;静态存储期贯穿程序执行 |
|
||
| ABI 约定下的寄存器与栈等 | 参数、局部状态和返回位置 | 常见实现的一次调用;内联可消除独立栈帧 |
|
||
| 分配存储区及指针 | 动态数据与访问地址 | `malloc`/`free` 等接口管理;C 不提供默认 GC |
|
||
| 库自建的回调表、队列或线程记录 | 外部事件与执行关联 | 由库或宿主定义,不能从 C 关键词推出 |
|
||
|
||
**调用与等待。** 执行调用表达式进入函数,再返回调用位置。保存函数指针不会自动运行函数。同步 I/O 可能阻塞线程;非阻塞 I/O 与事件循环需要操作系统和库的接口。`while` 可以写调度循环,也可以只是计算循环,两者用途不同。
|
||
|
||
**结束与关键词。** 自动存储期、静态存储期和分配存储期不同;离开函数不自动释放所有动态内存。`static`、`_Thread_local` 等影响存储属性,`return` 结束函数;`free`、`atexit` 是库名称。正常退出和强制终止的清理保证不同。完整入口例子见[普通进程路径](framework-runtime-lifecycles.md#路径一c-普通进程)。
|
||
|
||
## 6. C++
|
||
|
||
**入口与执行者。** 普通原生程序经启动及初始化进入 `main`,函数通过调用表达式执行。与 C 相比,C++ 特别区分存储的取得、对象生命周期开始、析构和存储释放;这些步骤不能只合称“对象在内存里”。[启动](https://eel.is/c++draft/basic.start)、[对象生命周期](https://eel.is/c++draft/basic.life)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 编译后的函数与类型相关信息 | 调用逻辑、布局及所需分派信息 | 编译实现管理,不要求所有类型都有运行时对象 |
|
||
| 普通调用状态与自动对象 | 参数、局部数据和退出时析构关系 | 正常离开及适用的异常展开触发析构 |
|
||
| 所有权容器与动态对象 | 指针及释放责任 | 如 RAII 包装器;原始指针本身不自动表达所有权 |
|
||
| coroutine state、promise 与 awaiter | 参数、跨暂停局部状态及继续协议 | 编译器与库共同管理,最终需要适当销毁协程状态 |
|
||
|
||
**进入与等待。** `co_await`、`co_yield`、`co_return` 参与协程协议。首次是否暂停由 promise 的 `initial_suspend` 等决定;等待是否真暂停由 awaiter 决定。C++ 不因为出现 `co_await` 就自带统一 scheduler 或创建线程。这里的 promise 是协程协议对象,不能直接等同于 JavaScript Promise。[协程定义](https://eel.is/c++draft/dcl.fct.def.coroutine)
|
||
|
||
**结束与关键词。** RAII 把资源退出关联到对象析构,`try/throw` 的异常展开可触发清理;异常终止等情形不能套用普通展开保证。协程逻辑完成后还可能停在最终暂停点,完成和 frame 释放不同;裸 handle 的遗失不自动等于安全销毁。`delete`、析构及所有权包装器负责相应存续,`constexpr` 等编译期能力不属于宿主事件调度。[析构函数](https://eel.is/c++draft/class.dtor)
|
||
|
||
典型协程轨迹:调用建立状态 → 按 initial_suspend 协议继续或暂停 → awaiter 检查与继续 → 逻辑完成 → 最终暂停及状态销毁按各自协议处理。scheduler 是库或宿主角色,不由协程关键词单独决定。
|
||
|
||
## 7. C#
|
||
|
||
**入口与执行者。** 以常见 .NET 应用为基线,编译产生程序集中的 CIL 和元数据,运行时通过 JIT 等路径执行;AOT 是另一种路径。入口可由 `Main` 或顶层语句等构成。CLR 管理执行与内存,不自动为所有应用规定 Unity 的帧循环。[托管执行过程](https://learn.microsoft.com/en-us/dotnet/standard/managed-execution-process)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 程序集、类型和方法元数据 | 可执行定义及类型关系 | 加载及运行环境管理 |
|
||
| 普通方法调用状态 | 参数、局部状态和返回关系 | 调用进入至返回或异常离开 |
|
||
| 编译器生成的 async 状态机 | 跨暂停局部状态、当前位置和 awaiter | 一次异步调用;具体表示可优化 |
|
||
| Task 与继续处理 | 完成状态、结果、异常等 | 可被多方等待;不等于一个专属线程 |
|
||
| SynchronizationContext/TaskScheduler 等 | 继续工作的执行策略 | 库、宿主和调用配置共同决定 |
|
||
|
||
**开始与等待。** 普通 `async Task` 方法被调用时会先同步推进;遇到尚未完成的 awaitable 才需要暂停。awaitable 已完成时可以继续同步推进,不能照抄 JavaScript `await` 的时间模型。继续位置保存在状态机中,恢复线程由捕获的上下文等规则影响,不保证总在原线程。[Task 异步模型](https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/task-asynchronous-programming-model)
|
||
|
||
**结束与关键词。** `return` 或异常使 Task 得到相应结果;取消是合作处理,不是自动回滚。`using`/`await using` 关联资源释放,`lock` 关联互斥,GC 管理内存可达性。Task 完成、资源 Dispose、对象回收是不同事件。[GC 基础](https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals)
|
||
|
||
Unity 是另一种宿主组合,具有 Mono/IL2CPP 后端及 PlayerLoop;其 `IEnumerator` 协程由引擎推进,不等于 C# async Task。[Unity 案例](framework-runtime-lifecycles.md#路径四unity)
|
||
|
||
## 8. Go
|
||
|
||
**入口与执行者。** 包变量初始化、`init` 函数及 `main.main` 有规定的启动关系。普通函数调用由当前 goroutine 执行;`go f(args)` 先在调用方求值函数及参数,再让新 goroutine 执行调用。Go 自带 runtime 管理 goroutine,不需要每个应用先自己编写事件循环。[Go 程序初始化与执行](https://go.dev/ref/spec#Program_initialization_and_execution)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 包、编译代码与初始化状态 | 定义、全局数据及启动顺序 | 程序初始化和运行 |
|
||
| goroutine 的栈及 g 记录 | 调用链、保存的执行状态及调度状态 | runtime 管理一次 goroutine 活动 |
|
||
| M 与 P | OS 线程、执行 Go 代码所需的运行时资源 | 当前 runtime 的 G/M/P 实现模型 |
|
||
| channel 与等待关系 | 缓冲、通信状态和发送/接收等待者 | 程序引用与 runtime 共同维持 |
|
||
| 堆对象 | 跨调用、跨 goroutine 的数据 | GC 跟踪可达性;不保证每个局部值都进堆 |
|
||
|
||
G/M/P 是具体 Go runtime 的实现说明;P 是调度资源,和用户权限没有关系。goroutine 不要求绑定一个永远专属的 OS 线程。[Go runtime HACKING](https://go.dev/src/runtime/HACKING)、[Go GC 指南](https://go.dev/doc/gc-guide)
|
||
|
||
**等待、结束与关键词。** channel 操作、`select` 等可让 goroutine 等待,runtime 保存状态并在条件合适时安排继续。`defer` 在外围函数退出时处理延后工作,不在每次等待时触发。`main` 返回会结束程序,不等待所有其他 goroutine 自行完成;应用需要明确等待和关闭安排。关闭 channel 表达通信协议,不等于强制杀死全部相关 goroutine。
|
||
|
||
典型轨迹:包初始化 → main → `go` 启动活动 → runtime 安排推进 → channel 条件不满足时等待 → 条件满足后继续 → 函数退出并执行 defer。main 是否仍在等待其他工作,由应用的同步逻辑决定。
|
||
|
||
## 9. Rust
|
||
|
||
**入口与执行者。** 普通原生程序从入口进入函数调用。所有权、借用与生命周期注解约束值的使用;生命周期注解通常是编译期关系,不是运行时事件回调。默认内存管理依赖所有权及 Drop,不要求跟踪式 GC。[所有权](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 编译后的函数与类型表示 | 调用逻辑和数据布局 | 编译器及链接环境管理 |
|
||
| 普通调用状态与所有权值 | 参数、局部值及退出释放责任 | 作用域及移动后的所有者决定存续 |
|
||
| async 生成的 Future 状态 | 跨等待局部数据和进度 | Future 的所有者持有;由 poll 推进 |
|
||
| Context/Waker | 通知 executor 某项工作应再获推进机会 | Future 与 executor 的恢复协议 |
|
||
| executor 的 task 与队列 | Future、调度状态及唤醒关联 | 所选异步运行库定义;标准语言不指定唯一 executor |
|
||
|
||
**进入与等待。** 调用 `async fn` 创建 Future,函数体在 Future 被推进时执行。`.await` 不自动创建独立并发任务;spawn 由运行库提供。`poll` 返回 `Pending` 或 `Ready`;Waker 通知需要再安排推进,不保证被唤醒就已经有最终结果,也不要求无限忙等。某些 Future 是已启动活动的结果句柄,不能从“Future 惰性推进”推断背后所有工作都尚未开始。[async 函数](https://doc.rust-lang.org/reference/items/functions.html#async-functions)、[Future 协议](https://doc.rust-lang.org/std/future/trait.Future.html)
|
||
|
||
**结束与关键词。** 正常作用域退出释放相应拥有的值;panic 的 unwind 与 abort 对析构的保证不同。丢弃 Future 通常停止进一步推进并释放它拥有的状态,但不能撤销已发出的远端操作。`move` 改变捕获方式,`async/await` 参与状态保存与推进,`loop` 本身不构成完整 executor。
|
||
|
||
典型轨迹:创建 Future → poll → Pending 时保存等待关系 → Waker 通知再调度 → 再次 poll 推进保存的状态 → Ready 交付结果。Future 结束后的内存与资源释放,还取决于所有者。
|
||
|
||
## 10. Swift
|
||
|
||
**入口与执行者。** 普通调用与应用入口由 Swift 编译代码及宿主启动;UI 应用还要服从应用框架。调用 async 函数不等于必然创建新的独立 Task。`await` 标记潜在暂停点,不保证每次遇到它都实际暂停。[Swift 官方语言书:并发](https://raw.githubusercontent.com/swiftlang/swift-book/main/TSPL.docc/LanguageGuide/Concurrency.md)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| 编译代码与类型信息 | 函数逻辑和类型关系 | 编译工具及运行环境管理 |
|
||
| 普通调用及 async 继续状态 | 参数、局部状态和暂停后进度 | 当前调用与任务活动期间 |
|
||
| Task、任务组和取消状态 | 并发活动、结果及父子管理关系 | 结构化并发作用域或非结构化任务安排 |
|
||
| actor/executor 管理 | 隔离及工作执行约束 | 并发运行时;actor 不等于专属线程 |
|
||
| ARC 管理的类实例 | 强、弱、无主引用及实例数据 | 引用关系决定存续;需要处理强引用环 |
|
||
|
||
**调度与恢复。** `async let`、任务组和 `Task` 等表达不同任务组织方式。潜在暂停允许其他工作推进,恢复后不能一般性假定 OS 线程不变。actor 保证相应隔离边界,跨 `await` 仍要考虑其他工作可能改变状态。executor 接收并安排可运行的 job,任务可以跨多个这样的执行片段存续。[Swift SE-0304 的 task、job 与 executor 模型](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0304-structured-concurrency.md#executors)
|
||
|
||
**结束与关键词。** 结构化子任务受外围作用域约束;取消是合作机制。`defer` 关联作用域退出,`deinit` 关联类实例销毁,`weak`/`unowned` 影响引用关系。ARC 主要解释类实例的内存管理,不应套用为所有值都对应独立引用计数堆对象。[Swift 官方语言书:ARC](https://raw.githubusercontent.com/swiftlang/swift-book/main/TSPL.docc/LanguageGuide/AutomaticReferenceCounting.md)
|
||
|
||
典型轨迹:建立任务作用域 → 调用 async 函数 → 潜在暂停点实际需要等待 → 保存继续状态 → 获得 executor 的继续机会 → 返回结果及完成作用域清理。任务存续跨越暂停,不是每次恢复都创建一份全新业务计算。
|
||
|
||
## 11. Kotlin
|
||
|
||
**入口与执行者。** 本节以 Kotlin/JVM 为基线:编译结果由 JVM 执行,普通函数有 JVM 调用状态。Kotlin/JS 和 Kotlin/Native 使用不同目标环境,不能把 JVM 栈和 GC 推广到所有 Kotlin 程序。`suspend` 表明函数可以参与可暂停调用,不自行创建一个新协程。[Kotlin 协程规范](https://kotlinlang.org/spec/asynchronous-programming-with-coroutines.html)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| JVM 方法与类元数据 | 编译后的逻辑及定义 | JVM 目标环境管理 |
|
||
| Continuation 与状态机 | 结果接收位置、跨暂停局部状态和进度标签 | 可暂停计算的继续关系;不是当前 OS 线程的别名 |
|
||
| CoroutineContext | dispatcher、Job 等上下文元素 | 协程库及继承、覆盖规则管理 |
|
||
| Job/Deferred | 活动状态、父子关系、取消及结果 | kotlinx.coroutines 的管理对象,不是语言关键词 |
|
||
| dispatcher 与等待管理 | 决定工作在哪种执行环境推进 | 所选库和宿主提供 |
|
||
|
||
**进入与等待。** `launch`、`async` 等 builder 由 kotlinx.coroutines 创建并组织活动;开始时机受启动模式影响。调用 suspend 函数可继续在现有协程中执行,不等于 spawn。编译器可将暂停路径转换为带局部字段及状态标签的继续结构;恢复可能更换线程,也可能依据 dispatcher 保持特定线程约束。[协程上下文与 dispatcher](https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html)
|
||
|
||
**结束与关键词。** Job 完成、子任务结束、取消传播及 `finally` 清理分别遵循协程库与语言规则。JVM 的 GC 管理内存,业务资源仍需合适的关闭协议。`fun`、`suspend`、`try/finally` 分别涉及定义、暂停能力和退出;`Job`、`launch`、`async` 在这里是库设施。
|
||
|
||
典型轨迹:builder 组织活动及上下文 → 按启动模式进入 → suspend 调用实际暂停时保存状态 → 条件满足后通过 Continuation 恢复 → 返回或异常 → Job 处理完成及子活动关系。继续逻辑和 dispatcher 的执行位置规则共同决定下一步。
|
||
|
||
## 12. Julia
|
||
|
||
**入口与执行者。** Julia REPL、脚本及 `include` 可以执行顶层代码。解析、宏展开和 lowering 后,由运行时选择解释或编译等路径处理;函数定义增加方法,不意味着当场执行函数体。调用根据方法分派选择逻辑,运行时可编译相应专门化。[Julia 执行路径](https://docs.julialang.org/en/v1/devdocs/eval/)
|
||
|
||
| 结构 | 保存什么 | 生命周期及归属 |
|
||
| --- | --- | --- |
|
||
| Module、方法及专门化相关表示 | 绑定、方法逻辑和可执行代码 | Julia 运行时管理 |
|
||
| 普通调用状态 | 参数、局部状态和返回关系 | 一次调用 |
|
||
| Task | 可暂停执行状态、结果及活动关系 | 由程序创建、运行时调度 |
|
||
| Channel 和等待者 | 通信内容及等待关联 | 任务与 Channel 协议管理 |
|
||
| 运行时值表示 | 值、类型及必要的对象头等 | 运行时管理;优化后的值不保证均是单独装箱对象 |
|
||
|
||
Julia 的 `jl_value_t` 等是实现层的对象表示;它回答值怎样存储,不决定哪个函数什么时候被调度。实现文档的历史细节不能直接充当完整稳定 ABI。[Julia 对象布局](https://docs.julialang.org/en/v1/devdocs/object/)
|
||
|
||
**进入与等待。** 创建 `Task` 不立即执行其函数体;需要调度或其他推进安排。`@async`、`Threads.@spawn` 等宏会创建并安排任务,但执行位置约束不同。`wait`、Channel 等可以让当前任务等待,运行时安排其他工作;暂停后保留状态,不从函数开头重新运行。[Julia 异步编程](https://docs.julialang.org/en/v1/manual/asynchronous-programming/)
|
||
|
||
**结束与关键词。** `return` 或异常结束本次调用,`try/finally` 关联退出清理。GC 与外部资源关闭分开处理;`do` 可把函数传给资源管理 API,具体关闭保证取决于被调用的 API。`Task`、`schedule`、`wait`、`Channel` 及这些宏不因为参与生命周期就都变成关键词。
|
||
|
||
典型轨迹:创建 Task → 调度 → 执行函数体 → wait/通信形成等待 → 条件满足后继续 → 返回或失败,等待者取得相应结果。Task 对象及结果可以在活动结束后继续被引用。
|
||
|
||
## 横向比较:什么动作真正进入执行
|
||
|
||
下表只比较所述常见用法。对象创建、提交调度和获得执行机会之间,不能用同一个“启动”掩盖差别。
|
||
|
||
| 用法 | 这一动作的直接效果 | 下一步由谁推进 |
|
||
| --- | --- | --- |
|
||
| 各语言的普通同步函数调用 | 当前执行路径进入函数体 | 当前线程/执行引擎继续 |
|
||
| Python `async def` 调用 | 创建 coroutine,未执行函数体 | await、Task 等驱动 |
|
||
| JavaScript `async` 调用 | 执行同步前缀,返回 Promise | 引擎及宿主协作推进异步继续 |
|
||
| C# 常规 `async Task` 调用 | 同步推进,必要时在未完成 awaitable 处暂停 | 状态机、awaiter 与上下文策略 |
|
||
| Rust `async fn` 调用 | 创建 Future | await 或 executor 的 poll |
|
||
| C++ 协程调用 | 建立协程协议及状态,首次暂停规则由 promise 决定 | 调用者、awaiter 或库的 scheduler |
|
||
| Go `go` 语句 | 求值调用信息并启动新的 goroutine | Go runtime 调度 |
|
||
| Swift async 调用 | 在当前任务中调用可暂停函数;不必创建新 Task | Swift 并发执行机制 |
|
||
| Kotlin suspend 调用 | 在可暂停调用链中执行;不必创建新 Job | Continuation 与协程上下文 |
|
||
| Julia `Task` 构造 | 创建任务,未运行函数体 | 调度及运行时推进 |
|
||
| React 状态更新 | 请求一次界面更新,不等于直接执行每个 Effect | React 组织渲染与提交,宿主提供执行机会 |
|
||
|
||
## 生命周期设计的比较结论
|
||
|
||
以下是基于上面机制的分析归纳,不是任何语言的统一规范。
|
||
|
||
- **循环不是所有生命周期的必要形式。** C++ 自动对象可以随作用域构造和析构;普通调用可以直接进入并返回。循环适合持续接受工作,但不定义全部存续关系。
|
||
- **钩子不是全部调度机制。** 钩子连接条件与逻辑;优先级、执行位置、等待及取消还需要独立规则。命名回调、函数参数、订阅和协程继续位置是不同的连接形式。
|
||
- **Task 不等于整个对象体系。** 数据值、代码定义、一次执行、结果句柄和长期组件应分别说明。可以共享基础对象模型,但不能因此共享同一执行状态机。
|
||
- **宏任务/微任务是特定宿主的顺序契约。** 是否采用此类分层,应由可观察顺序、更新边界及响应性需求决定,不能从“需要循环”直接推出。
|
||
- **长期保存定义不等于长期执行。** 会话、模块、平台对象和订阅都可以存续而暂时没有用户代码运行。
|
||
|
||
Endorphin 已有的阶段定义见 [Preflight 与 inflight](../guide/05-preflight-and-inflight.md),普通调用见[函数与调用](../guide/09-functions-and-calls.md),会话提案见 [REPL](../guide/14-repl.md)。需要进一步明确的执行契约集中在[框架比较后的设计分析](framework-runtime-lifecycles.md#对-endorphin-执行模型的设计分析)。
|
||
|
||
[关键词手册](language-keywords.md) · [框架如何接管代码执行](framework-runtime-lifecycles.md) · [返回目录](../README.md)
|