PHP 新 RFC 即将到来!异步调度器正式进入 PHP 核心

AI 概述
一份PHP新RFC提案提议在内核内置协程调度器,弥补现有Fiber上下文切换复杂的短板。调度器通过三类核心接口管理协程生命周期,统一把PHP主脚本视作协程,标准化并发执行模型。依托调度器可改造sleep等标准库函数实现异步非阻塞IO,还配套协程上下文机制隔离各协程执行状态,解决缓冲区、请求数据错乱问题,同时优化GC、报错等内核组件适配协程,为PHP原生异步生态奠定底层基础。
目录
文章目录隐藏
  1. 什么是调度器?它为什么是必要的?
  2. TrueAsync 是如何使用调度器的
  3. 为什么调度器应该被包含在 PHP 的核心代码中呢?
  4. 异步标准库
  5. 超越调度
  6. 执行上下文

PHP 新 RFC 即将到来!异步调度器正式进入 PHP 核心

一项新的 RFC 提案即将提出,该提案旨在进一步推进异步执行功能在 PHP 核心中的应用。

什么是调度器?它为什么是必要的?

调度器是负责管理协程生命周期的组件。它负责创建、调度、切换和销毁协程,能够检测死锁情况,并驱动应用程序的主事件循环。换句话说,调度器充当了协程的主管角色。

Fiber API 通过依赖非对称上下文切换的方式来弥补缺乏此类管理器的缺陷。虽然这种方法功能上可行,但复杂程度却相当高。

在多个 Fiber 之上运行时,每个协程都必须执行一次额外的上下文切换,以在当前正在执行的 Fiber 与所有其他 Fiber 所源自的根 Fiber 之间切换。因为 Fiber 并不允许任意的上下文切换。虽然 Revolt 试图通过优化来让多个协程共享相同的 Fiber,但它无法消除这一根本限制。

在设计层面,调度器完全避免了这个问题。其 API 可以概括如下(伪代码):

interface SchedulerAPI {
    public function newCoroutine(callable $function): Coroutine;
    public function suspend(): void;
    public function enqueue(Coroutine $coro): void;
}

只需要使用三种核心方法即可:

  • newCoroutine()创建一个新的协程。
  • enqueue()函数将一个协程放入调度器的就绪队列中。
  • suspend()会将执行任务返回给调度器。

TrueAsync 是如何使用调度器的

在内部,TrueAsync API 是建立在 Scheduler API 之上的。

这就是 spawn()函数的运作方式:

function spawn(callable $fun): Coroutine {
    $coro = $scheduler->newCoroutine($fun);
    $scheduler->enqueue($coro);
}

这个函数只是创建一个协程,然后安排其执行。当协程暂停时:

function suspend(): void
{
    $next = $this->queue->dequeue();
    $this->switchTo($next);
}

调度器从就绪队列中取出下一个待执行的协程,并将执行任务转移到该协程上。

为什么调度器应该被包含在 PHP 的核心代码中呢?

有几个重要的原因。

从历史上看,PHP 与其他大多数编程语言的不同之处在于它并没有明确的入口点。这里没有名为main()的函数,而是直接以某个脚本文件(如 index.php)的执行开始。

例如:

<?php

echo "php start\n";
echo "php end\n";

打印完成后,执行自然就会结束:

php start
php end

然而,一旦引入了协程机制,这种模型就不再适用了。

<?php

echo "php start\n";

spawn(fn() => print("here\n"));

echo "php end\n";

打印

php start
php end
here

即使主执行流程已经结束,PHP 进程仍然会继续运行,因为还存在可执行的协程。

这正是预期中的行为:主要的执行流程本身应该被表示为一个协程。

从概念上讲,像这样的代码:

<?php

echo "start\n";

应该隐含地执行与以下操作相当的操作:

createCoroutine(...)

这使得 PHP 的执行模型变得标准化,实际上让 PHP 成为一种具有内置并发性的语言。

将主要执行流程视为另一个协程是这份 RFC 的主要目标之一。

异步标准库

RFC 的另一个主要目标是让 PHP 标准库能够直接支持异步执行功能。

允许现有的 PHP 函数能够在异步代码中以非阻塞方式运行,这为开发者节省了无数时间,同时也为 PHP 生态系统带来了全新的功能。

让我们来看看 sleep()函数的以下伪代码实现:

php_async_sleep(int $seconds)
{
    $coro = current_coroutine();

    async_io_timer_new(
        $seconds * 1000,
        /* period */ 0,
        function () use ($coro) {
            $scheduler->enqueue($coro);
        }
    );

    $scheduler->suspend();
}

这个实现展示了如何利用 Reactor API 使得sleep()函数变为异步操作。

有两个重要的步骤:

  • 创建一个计时器,并注册一个回调函数,在计时器到期时重新排队并执行暂停的协程。
  • 通过调用suspend()函数,明确地将执行权交给调度器处理。

要使这一功能生效,PHP 内核本身必须能够理解调度器接口。

一旦这个基础结构建立起来之后,所有的阻塞 I/O 操作都可以被修改,使其能够在原本需要等待的地方暂停执行。这正是 TrueAsync 在 PHP 核心代码中实现的机制——通过在每个阻塞 I/O 操作点插入协程上下文切换,从而实现这一功能。

超越调度

调度器 API 还对几个核心引擎组件进行了重要的修改。

例如

  • 垃圾收集现在能够意识到协程的存在;
  • 救助操作的执行是在其自己的协程中进行的;
  • 该引擎表现为一个统一的并发运行环境,而不是由多个独立且同步的子系统组成的系统。

执行上下文

仅使用协程还不足以实现完全并发的运行环境。

许多 PHP 功能依赖于某种执行状态,而该执行状态必须始终与特定的协程相关联。

一个很好的例子就是ob_start()函数,它会将输出数据累积到一个缓冲区中,之后再进行处理或稍后返回。

尝试将ob_start()与 Fibers 或 AMPHP 一起使用会导致不可预测的行为,许多依赖于全局执行状态的 API 也会产生类似的问题。

为了实现确定性行为,PHP 内核需要一种能够在切换协程时保持执行状态的内部机制。

这种机制被称为协程上下文。

调度器 API 定义了两种协程上下文:

  • 内部上下文,适用于高层管理人员的使用以及核心功能的开发。
  • 用户上下文,适用于各种框架和应用场景。

用户上下文使得框架和服务能够将任意状态与当前执行的协程关联起来——例如,请求范围内的数据。

协程上下文是使现有框架能够顺利适应异步 PHP 运行环境的核心机制。

以上关于PHP 新 RFC 即将到来!异步调度器正式进入 PHP 核心的文章就介绍到这了,更多相关内容请搜索码云笔记以前的文章或继续浏览下面的相关文章,希望大家以后多多支持码云笔记。

「点点赞赏,手留余香」

19

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 admin@mybj123.com 进行投诉反馈,一经查实,立即处理!
重要:如软件存在付费、会员、充值等,均属软件开发者或所属公司行为,与本站无关,网友需自行判断
码云笔记 » PHP 新 RFC 即将到来!异步调度器正式进入 PHP 核心

发表回复