PHP True Async 最近进展以及背后的争议

PHP True Async 最近进展以及背后的争议

PHP True Async 团队还在努力。如果 RFC 通过,将会跟着 PHP 8.6 一起发布。现在RFC 1.6 刚刚进入投票阶段,RFC 1.7 就已经准备就绪。最大的变化是:将 Fiber 作为协程生成器编织进 TrueAsync,并使用显式的 yield(Fiber::suspend)。这一优雅的改动归功于 Bob Weinand(详见 RFC:https://wiki.php.net/rfc/true_async#fiber_support)。

但真正有趣的事情发生在幕后。

原文链接 PHP True Async 最近进展以及背后的争议

WordPress 兼容性争议

在激烈的 TrueAsync RFC 1.6 辩论期间,有一个论点是异步对 PHP 不利,因为它"破坏了 WordPress"。逻辑是:WordPress 依赖全局变量,所以在协程内调用其 API 可能会破坏某些东西。

这是真的吗?作者决定用最糟糕的方式找出答案:将 WordPress 作为有状态应用运行在 TrueAsync 上。这不是第一次尝试——GitHub 上已经有使用 Swoole 实现的项目。困难的部分不在于异步,而在于 WordPress 被设计为在每个请求上"死掉"。重新定义常量、重新包含文件、到处都是全局状态。WordPress 并不是有状态生命周期的理想候选者。

这是否阻止了尝试?当然没有。为了实现这一点,作者构建了一个自定义的 TrueAsync 和 PHP,使全局变量对每个协程唯一,超全局变量对每个 Async\Scope 唯一。

翻译一下:当你在协程内运行代码时,你无法通过全局变量意外地传递数据。作用域本地的超全局变量为接触 $_GET$_POST$_SESSION 等的协程创建了一个沙箱,而不会泄漏到其他请求中。这让你可以在一个进程中运行多个 WordPress 副本,几乎不需要任何更改。可怕吗?绝对的。

第一项工作:一个入口点,初始化 WordPress、数据库连接,并将控制权传递给模板层:

include_once WP_ROOT . '/wp-config.php';
include_once WP_ROOT . '/wp-settings.php';

ob_start();

// Run WordPress
wp();

$template_loader = ABSPATH . WPINC . '/template-loader.php';
if (file_exists($template_loader)) {
    include $template_loader;
}

$output = ob_get_clean();

$responseHeaders = [
    'Content-Type' => 'text/html; charset=UTF-8',
    'Content-Length' => strlen($output),
    'X-Powered-By' => 'TrueAsync PHP',
];

$success = sendResponse($client, 200, $responseHeaders, $output, $shouldKeepAlive);
ServerStats::$requestHandled++;

// Close MySQL connection to prevent connection leaks
closeMySQLConnection();

return $success ? $shouldKeepAlive : false;

思路很简单:

  • 为每个请求启动一个新的 WordPress。
  • 打开一个唯一的数据库连接。
  • 使用 ob_start/ob_get_clean 捕获 WordPress 输出并发送回去。

要让它工作,你必须从 WordPress 中移除一些 exit/die 调用。之后,你就有了一个可以处理 WordPress 请求的入口点。

但如果我们只初始化 WordPress 一次,然后将其状态克隆到每个请求的协程中呢?如果这样可行,我们可以只克隆 WordPress 的部分内容:当数据不可变时,为多个并发请求重用相同的内存。

添加一个协程来监控进程统计信息,而不会拖慢服务器:

/**
 * Statistics reporter coroutine
 */
spawn(function(): void {
    while (true) {
        delay(5000);
        $activeCoroutines = count(getCoroutines());
        $activeConnections = ServerStats::$connectionsStarted - ServerStats::$connectionsClosed;

        echo "[Stats] Connections: Accepted=" . ServerStats::$connectionsAccepted .
             " Started=" . ServerStats::$connectionsStarted .
             " Active=" . $activeConnections .
             " Closed=" . ServerStats::$connectionsClosed .
             " | Requests: Total=" . ServerStats::$requestCount .
             " Handled=" . ServerStats::$requestHandled .
             " | Coroutines: " . $activeCoroutines . "\n";
    }
});

令人惊讶的是:大量旧代码就这样工作了。当多个协程访问缓存代码时会出现问题。WordPress 动作处理程序在钩子分发期间有时需要本地状态。需要进行更改。你无法避免它们。

尽管如此……很难忽视有多少代码能够原样运行。并发服务器为任何 PHP 代码提供了以合理代价获得真正性能提升的机会。

并发服务器

PHP 并没有自带并发服务器(除了 Swoole 和基础的 HTTP/1.1 服务器)。如果你无法使用协程,为什么要有协程?用纯 PHP 编写自己的服务器是痛苦的,特别是对于 HTTP/2 或 HTTP/3。

RoadRunner 和 FrankenPHP 团队依靠 Go 来实现性能。让我们看看 TrueAsync 如何适配。

要将 TrueAsync 与 FrankenPHP 集成,PHP 需要一个思维转变:它永远存活,服务器变成了 PHP 驱动的插件:

<?php

use FrankenPHP\HttpServer;
use FrankenPHP\Request;
use FrankenPHP\Response;

set_time_limit(0);

echo "Starting FrankenPHP TrueAsync HttpServer...\n";

// Register request handler
HttpServer::onRequest(function (Request $request, Response $response) {

    $method = $request->getMethod();
    $uri = $request->getUri();
    $headers = $request->getHeaders();
    $body = $request->getBody();

    $response->setStatus(200);
    $response->setHeader('Content-Type', 'application/json');

    $responseData = [
        'message' => 'Hello from FrankenPHP TrueAsync!',
        'method' => $method,
        'uri' => $uri,
        'total_coroutines' => count(\Async\get_coroutines()),
        'memory' => round(memory_get_usage(true) / 1024 / 1024, 2),
        'timestamp' => date('Y-m-d H:i:s'),
        'headers_count' => count($headers),
        'body_length' => strlen($body),
    ];

    $response->write(json_encode($responseData, JSON_PRETTY_PRINT));
    $response->end();
});

echo "Request handler registered. Event loop is running.\n";
// Script stays loaded, event loop handles requests

现在我们可以构建跳过每次请求都重新初始化所有内容的应用:
PHP True Async 最近进展以及背后的争议
有状态应用

我们可以在并发协程之间共享这些预热的服务:
PHP True Async 最近进展以及背后的争议

TrueAsync 为高性能 PHP 应用打开了大门,通过混合有状态架构和并发处理,可以与其他语言竞争。

下一步是什么?

RFC 1.7 即将进入讨论阶段,更多实验正在进行中。例如:使用 TrueAsync 和 FrankenPHP 运行 Laravel:https://github.com/true-async/laravel-test

posted @ 2025-12-26 07:58  JaguarJack  阅读(5)  评论(0)    收藏  举报