首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PHP 异步 RFC:标准化引擎级并发调度器 ABI

PHP 异步 RFC:标准化引擎级并发调度器 ABI

作者头像
Tinywan
发布2026-07-27 12:47:14
发布2026-07-27 12:47:14
150
举报
文章被收录于专栏:开源技术小栈开源技术小栈

核心定位

这是一个纯引擎内部机制的 RFC,不添加任何 PHP 可见的类、函数、常量或语法。它的目的是在 PHP 引擎层面建立一个可插拔的协程调度器接口(ABI),让扩展可以提供调度器,从而激活 PHP 的并发执行模式。

注意:这里说的"并发"是单线程协作式的,不是多线程并行。所有执行流都在一个 OS 线程内,通过协程切换实现交错执行。

RFC:https://wiki.php.net/rfc/async_scheduler_abi

背景与动机

  • PHP 8.1 引入了 Fiber(纤程)作为底层原语,但没有调度器——何时运行什么、按什么顺序,完全交给用户态处理。
  • 结果是 ReactPHP、Revolt、AMPHP 等框架各自维护独立的事件循环和协程抽象,互不兼容。
  • 这个 RFC 填补了这个空白:在引擎层面定义一个调度器插槽,让扩展可以注册一个调度器来驱动协程。

提案核心:调度器 ABI

引擎与调度器之间通过六个通知点(notifications)交互。引擎自己不调度,所有调度决策都由调度器通过通知的返回值来做出。

六个引擎通知点

通知

触发时机

调度器职责

Launch

调度器启动时(脚本第一行前,或注册时)

返回一个"主协程"对象,顶层脚本在其中运行

Suspend

当前执行流挂起,把控制权交还给调度器

管理就绪队列,决定接下来运行哪个协程;主协程结束时需返回一个新的主协程给后续代码(如析构函数、shutdown 函数)

Enqueue

协程变为可运行状态(新建或恢复)

将协程加入就绪队列;可附带一个错误(用于取消、超时、I/O 失败)

Foreign Fiber

第三方代码创建的 Fiber 启动时(调度器已激活)

决定是"收养"该 fiber 到自己的调度中,还是让它保持为普通低层 fiber

Defer

一次性微任务被提交时

在下一个调度 tick 中执行该任务

Shutdown

并发需要提前终止时(如某个协程调用了 exit())

停止接受新工作,决定是完成剩余协程还是取消它们

关键机制

  • 对称切换(Symmetric Switch):不同于 Fiber 的"不对称"(只能 yield 给恢复者),这里 A 可以直接切换到 B,减少切换开销。
  • 协程生命周期created → queued → running → suspended → finished,可循环。
  • 主协程(Main):顶层脚本本身作为一个协程运行,结束时会被替换而非回收,以便后续 shutdown 代码继续运行。

协程上下文存储

引擎为每个协程维护两个存储,解决全局状态在并发下的安全问题:

  1. Internal Store(内部存储):供引擎和 C 扩展使用,键是数字 ID,值是原始 C 数据,对 PHP 不可见。用于存放如 ob_start() 的处理器栈、gethostbyname() 的结果缓冲区等。
  2. Userland Store(用户态存储):存普通 PHP 值(字符串/对象作键),是否暴露给 PHP 由调度器扩展决定。

存储机制已实现,但将 ob_start() 等核心子系统迁移到 per-coroutine 状态是后续工作。

与现有生态的关系

方面

影响

无调度器注册时

PHP 行为完全不变,零成本

Fiber 库(ReactPHP/Revolt/AMPHP)

现有代码无需改动;调度器可选择"收养"它们的 fiber,让它们运行在同一个调度上

调试工具(Xdebug 等)

协程执行上下文基于现有 Fiber 机制,step debugging 和堆栈跟踪继续工作

Opcache / JIT

无影响,无新 opcode

SAPI(CLI/FPM 等)

无可见影响

向后不兼容的变更(仅当调度器激活时)

  1. Fiber::suspend() 在析构函数中抛出错误
    • 调度器激活时,GC 析构阶段在一个专用协程中运行,该协程不是 fiber,因此从中调用 Fiber::suspend() 会抛出 FiberError
    • 这是有意为之的权衡:让析构阶段可预测,避免任意阻塞某个执行流。
  2. gc_collect_cycles() 可能因新原因返回 0 专用协程被取消,或无法创建专用协程时,返回 0
  3. 进程 fork 被限制 调度器激活时,pcntl_fork() 默认抛出错误,除非调度器注册了专门的 fork 处理程序。

明确不在这个 RFC 中的内容

这个 RFC 刻意保持最小化,只定义引擎与调度器的接缝。以下属于后续工作

  • 非阻塞 I/O:socket、文件、DNS、定时器的异步层
  • 用户可见的 API:如 spawn()await()、channels、futures、Async\ 命名空间等
  • 具体调度器实现:引擎不内置任何调度器或事件循环
  • 抢占式调度:所有切换都是显式的;抢占需要基于引擎现有中断机制的额外工作
  • Goroutine 式 M:N 调度 / 并行执行:PHP 的运行时架构(每线程独立对象存储、非原子引用计数)决定了这不可行
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 核心定位
  • 背景与动机
  • 提案核心:调度器 ABI
    • 六个引擎通知点
    • 关键机制
  • 协程上下文存储
  • 与现有生态的关系
  • 向后不兼容的变更(仅当调度器激活时)
  • 明确不在这个 RFC 中的内容
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档