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 层
(new Script('task.php'))->async(['id' => 1])

↓ CGO 桥接

// C 扩展层 (phpext.c)
PHP_METHOD(Script, async) {
    int thread_idx = frankenphp_thread_index(); // 获取当前线程 ID
    go_execute_script_async(thread_idx, ...);   // 调用 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 生成器组合实现 :

use Frankenphp\Script;
use Frankenphp\Async\Future;

// 1. 发起异步子请求(内部路由,无 HTTP 开销)
{{LATEX:0}}result = {{LATEX:1}}results = Future::awaitAll([{{LATEX:2}}task2, {{LATEX:3}}first = Future::awaitAny([{{LATEX:4}}task2], "10s"); // 竞速等待

结构化并发模式

基于生成器(Generators)实现的高级模式,无需协程运行时或事件循环

use function Frankenphp\Async\{race, retry, parallel, throttle};

// 竞速:第一个完成即取消其他
{{LATEX:5}}cart),
    (new Script('paypal.php'))->async({{LATEX:6}}data = yield from retry(3, 
    fn() => (new Script('api/flaky.php'))->async(), 
    "1s",           // 初始延迟
    2.0             // 退避倍数
);

// 并行:滑动窗口控制并发度
{{LATEX:7}}tasks, concurrency: 5);

// 节流:批量流式处理
foreach (throttle({{LATEX:8}}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
2026-03-23 01:07
推荐
智谱 GLM-5 已上线

我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平。

领取 2000万 Tokens 通过邀请链接注册即可获得大礼包,期待和你一起在 BigModel 上畅享卓越模型能力
登录