Loading...
正在加载...
请稍候

FrankenAsync 深度技术研究报告

✨步子哥 (steper) 2026年03月22日 11:21
## 项目概述 **FrankenAsync** 是由 Johan Janssens 开发的实验性并发框架,作为 **ConFoo 2026** 会议演讲《PHP 150x Faster, Still Legacy-Friendly》的配套参考实现 。它展示了如何利用 FrankenPHP 的线程模型实现**真正的并行执行**,而无需重写阻塞式 PHP 代码。 > **重要提示**:官方明确声明这是**参考实现/概念验证**,而非生产级框架。代码采用 MIT 许可,鼓励社区探索、fork 和适配 。 --- ## 核心架构解析 ### 1. 双层并发控制模型 FrankenAsync 通过两个关键机制控制并发: | 层级 | 技术实现 | 配置项 | 默认值 | 作用 | |------|---------|--------|--------|------| | **PHP 执行层** | 固定线程池 | `FRANKENASYNC_THREADS` | `4 × CPU` | 承载实际 PHP 脚本的 FrankenPHP 线程池 | | **Go 调度层** | 信号量滑动窗口 | `FRANKENASYNC_WORKERS` | `threads - 2` | 限制并发 Go goroutine 数量,超限时排队 | **工作原理**:当任务数超过信号量限制时,多余任务会在 Go 层队列中等待,形成**滑动窗口**执行模式 。 ``` 执行流程: PHP Script::async() → C 扩展 → Go Task Manager (信号量控制) → FrankenPHP 线程池 ↑ ↓ └────────── Future::awaitAll() ← 结果收集 ← 子请求执行完毕 ``` ### 2. 关键技术突破:跨语言调用链 FrankenAsync 最大的技术挑战是**从 PHP 用户态穿越到 Go 运行时**。标准 FrankenPHP 未暴露线程内部机制,因此项目依赖一个 Fork 版本,添加了关键 API : | 新增 API | 语言 | 作用 | |---------|------|------| | `frankenphp.Thread(index)` | Go | 通过索引获取 PHP 线程的 `*http.Request` 上下文 | | `frankenphp_thread_index()` | C | 从 C 扩展获取当前线程索引,实现双向桥接 | **完整调用链示例**: ```php // PHP 层 (new Script('task.php'))->async(['id' => 1]) ``` ↓ CGO 桥接 ```c // C 扩展层 (phpext.c) PHP_METHOD(Script, async) { int thread_idx = frankenphp_thread_index(); // 获取当前线程 ID go_execute_script_async(thread_idx, ...); // 调用 Go 函数 } ``` ↓ Go 运行时 ```go // Go 层 (phpext.go) func go_execute_script_async(index C.int, ...) { req := frankenphp.Thread(int(index)) // 获取请求上下文 manager := asynctask.FromContext(req.Context()) manager.Async(runnable) // 提交到信号量控制的任务队列 } ``` --- ## API 设计与使用模式 ### 核心原语:Script + Future FrankenAsync 仅暴露两个核心原语,其余功能通过标准 PHP 生成器组合实现 : ```php use Frankenphp\Script; use Frankenphp\Async\Future; // 1. 发起异步子请求(内部路由,无 HTTP 开销) $future = (new Script('api/task.php'))->async(['id' => 123]); // 2. 等待结果(支持超时) $result = $future->await("5s"); // 3. 批量等待 $results = Future::awaitAll([$task1, $task2, $task3], "30s"); $first = Future::awaitAny([$task1, $task2], "10s"); // 竞速等待 ``` ### 结构化并发模式 基于生成器(Generators)实现的高级模式,**无需协程运行时或事件循环** : ```php use function Frankenphp\Async\{race, retry, parallel, throttle}; // 竞速:第一个完成即取消其他 $payment = yield from race([ (new Script('stripe.php'))->async($cart), (new Script('paypal.php'))->async($cart), ], "10s"); // 重试:指数退避 $data = yield from retry(3, fn() => (new Script('api/flaky.php'))->async(), "1s", // 初始延迟 2.0 // 退避倍数 ); // 并行:滑动窗口控制并发度 $results = yield from parallel($tasks, concurrency: 5); // 节流:批量流式处理 foreach (throttle($allIds, 'task.php', batch: 50) as $result) { // 每批 50 个任务完成时产出结果 } ``` --- ## 性能特征与基准测试 ### 声称的性能数据 根据项目文档,FrankenAsync 实现了 **150x+ 的速度提升** ,测试场景为: - **任务类型**:模拟 I/O 阻塞(`usleep`)或真实 HTTP 请求(`local=0`) - **并发度**:通过 Go 信号量动态调节(默认 `threads - 2`) - **线程规模**:已测试支持数百线程(最高 500 线程) ### 与 ReactPHP/Amp 的对比 | 维度 | FrankenAsync | ReactPHP / Amp | |------|-------------|---------------| | **并发模型** | 操作系统线程并行(真并行) | 事件循环 + 协程(伪并行) | | **代码改造** | **零改造**,阻塞代码直接运行 | 需重写为异步模式(Promise/Fiber) | | **运行时依赖** | FrankenPHP (Go) + ZTS PHP | 纯 PHP 扩展(uv/libevent) | | **适用场景** | CPU 密集型 + I/O 密集型混合 | 纯 I/O 密集型、高并发连接 | | **生态兼容** | 任意现有 PHP 代码、框架 | 需专用异步库(异步 DB、HTTP 客户端) | | **内存隔离** | 子请求级隔离(线程安全) | 协程级共享内存 | **关键差异**:ReactPHP/Amp 需要 "异步思维" 重写代码,而 FrankenAsync 采用 **"编排而非重写"(Orchestrate, Don't Rewrite)** 理念,现有阻塞代码(如 `file_get_contents()`、DB 查询)无需修改即可并行执行 。 --- ## 技术限制与先决条件 ### 1. 构建要求 - **ZTS(Zend Thread Safe)PHP**:必须使用线程安全版本,通过 `static-php-cli` 自动构建 - **CGO 环境**:需要完整 CGO 编译器标志(`CGO_CFLAGS`/`CGO_LDFLAGS`) - **Go 1.26+**:使用最新的 CGO 特性 ### 2. 架构限制 - **Fork 依赖**:当前必须依赖 FrankenPHP 的分支版本(添加 `Thread(index)` API),上游尚未合并 - **Worker 模式冲突**:与 FrankenPHP 的 Worker 模式(常驻内存)不同,FrankenAsync 每个子请求都是**全新的 PHP 上下文**,适用于无状态任务 ### 3. 资源消耗 - **线程开销**:每个并发任务占用一个完整 OS 线程(而非协程的轻量级),高并发场景(>1000)内存消耗显著高于 ReactPHP - **信号量瓶颈**:`FRANKENASYNC_WORKERS` 默认设为 `threads - 2`,避免线程池耗尽 --- ## 项目结构解析 ``` frankenasync/ ├── main.go # HTTP 服务器入口,FrankenPHP 初始化 ├── asynctask/ # Go 任务管理器核心 │ ├── manager.go # 信号量、任务生命周期、goroutine 池 │ ├── manager_option.go # 配置选项 │ └── context.go # 请求上下文传递 ├── phpext/ # C 扩展 + Go 桥接层(关键技术点) │ ├── phpext.go # Go 导出函数(CGO) │ ├── phpext.c # PHP 类注册(Script/Future) │ ├── phpext.h # PHP 类声明 + arginfo │ └── phpext_cgo.h # CGO 桥接头文件 ├── examples/ │ ├── lib/async.php # 结构化并发辅助函数(race/retry/parallel) │ └── include/task.php # 阻塞任务示例 └── build/php/ # ZTS PHP 自动构建脚本(static-php-cli) ``` --- ## 总结与评估 ### 优势 1. **遗留系统友好**:唯一无需重写代码即可实现并行的 PHP 方案 2. **真并行**:利用多核 CPU,而非单核事件循环 3. **类型安全**:Future 错误通过异常传播(`FutureTimeoutException` 等),而非回调地狱 ### 局限 1. **实验性质**:明确声明非生产框架,API 可能剧烈变动 2. **资源密集**:线程模型比协程更重,不适合数万级高并发连接 3. **生态割裂**:需要特定的 FrankenPHP Fork 和 ZTS 构建 ### 适用场景 - **微服务编排**:并行调用多个下游服务(商品详情页聚合) - **批量数据处理**:CPU 密集型任务并行(图片处理、报表生成) - **遗留现代化**:不愿重写的老代码提速 FrankenAsync 代表了 PHP 并发编程的**第三条道路**:介于传统多进程(PHP-FPM)和异步协程(ReactPHP)之间的**线程级并行**,特别适合混合 I/O 和 CPU 密集型、且不愿重写代码的场景。

讨论回复

1 条回复
✨步子哥 (steper) #1
03-23 01:07
https://github.com/johanjanssens/frankenasync