Skip to content

Fiber 协程

workerman 从 5.0.0 开始支持 Fiber 协程 (纤程)

注意 Fiber 特性需要 PHP>=8.1 并安装 composer require revolt/event-loop ^1.0.0

介绍

Fiber 是 php 内置的协程 (纤程),它可以中断 PHP 代码,然后在需要的时候恢复其运行。它的最大作用是让开发者可以用同步的方式写异步非阻塞代码,这极大地增强了代码的可维护性。

示例

下面通过示例来对比协程和异步回调编程的区别。 假设我们有一个需求要求需要调用一个 HTTP 接口,然后延迟一秒响应,异步回调及协程写法分别如下。

异步回调用法

php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Http\Client;

$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = static function($connection, $request)
{
    static $http;
    $http = $http ?: new Client();
    // 请求HTTP接口
    $http->get('http://example.com/', function ($response) use ($connection) {
        // 延迟一秒发送
        Timer::add(1, function() use ($connection, $response) {
            // 向浏览器发送数据
            $connection->send((string)$response->getBody());
        }, null, false);
    });
};

Worker::runAll();

协程用法

php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Workerman\Timer;
use Workerman\Http\Client;

$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = static function($connection, $request)
{
    static $http;
    $http = $http ?: new Client();
    // 调用HTTP接口
    $response = $http->get('http://example.com/');
    // 延迟1秒
    Timer::sleep(1);
    // 发送数据
    $connection->send((string)$response->getBody());
};

Worker::runAll();

注意 以上代码需要安装 composer require workerman/http-client ^2.0.0

这两种写法都是异步非阻塞执行的,运行效率都很好,但是协程用法比异步回调更容易阅读,更容易维护。

Fiber 注意事项

  • Fiber 协程并不支持将 Pdo、Redis、及 PHP 内部阻塞函数协程化,也就是说使用这些扩展及函数仍然是阻塞式调用
  • 目前可用的 Fiber 协程客户端有 workerman/http-clientworkerman/redis

Swoole 协程

workerman v5 同时支持将 Swoole 作为底层事件驱动

php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Workerman\Worker;
use Swoole\Coroutine\Http\Client;
use Swoole\Coroutine;

// 这里需要手动将Swoole设置为底层事件驱动
Worker::$eventLoopClass = Workerman\Events\Swoole::class;
$worker = new Worker('http://0.0.0.0:12345');
$worker->onMessage = static function($connection, $request)
{
    Coroutine::create(function() use ($connection) {
        $cli = new Client('example.com', 80);
        $cli->get('/get');
        $connection->send($cli->body);
    });
};

Worker::runAll();

提示

  • 建议使用 swoole5.0 或者后续更高版本
  • 把 swoole 作为底层事件驱动可以让 workerman 支持 swoole 的协程
  • 使用 swoole 作为底层事件驱动时可以不安装 event 扩展
  • swoole 默认没有开启一键协程,也就是说基于 Pdo、Redis、PHP 内置文件读写是阻塞式调用
  • 如需开启一键协程,需要手动调用 \Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL);

更多请参考 Swoole 手册

更多请参考事件驱动

关于协程

首先没必过份迷信协程,当数据库、Redis 等存储都在内网时,多进程阻塞式调用很多时候比协程更快。从 techempower.com 3 年来的压测数据来看 workerman 阻塞式数据库调用性能要优于 swoole 数据库连接池 + 协程,甚至比 go 语言的 gin、echo 等协程框架性能高近 1 倍。

workerman 已经将 PHP 应用的性能提升了数倍甚至数十倍,绝大部分 workerman 项目加上协程可能不会有更大的性能提升。 如果你的系统里有慢调用,例如有外部 HTTP 调用,可以考虑使用协程来提升性能。

基于 MIT 许可发布