PHP Fiber纤程怎么用

wen PHP项目 4

本文目录导读:

PHP Fiber纤程怎么用

  1. 基本概念
  2. 基本用法示例
  3. 实用场景示例
  4. 重要方法和状态
  5. 异常处理
  6. 与生成器的对比
  7. 注意事项

PHP Fiber(纤程)是 PHP 8.1 引入的协程实现,允许你在单线程内实现类似多线程的并发执行,我来详细介绍它的用法。

基本概念

Fiber 是可中断可恢复的函数,类似于生成器,但更强大:

// 创建一个 Fiber
$fiber = new Fiber(function() {
    echo "Fiber 开始执行\n";
    // 暂停执行,返回控制权给调用者
    Fiber::suspend("暂停值");
    echo "Fiber 恢复执行\n";
    return "完成值";
});
// 启动 Fiber
$value = $fiber->start();
echo "收到: $value\n";
// 恢复 Fiber 执行
$value = $fiber->resume();
echo "收到: $value\n";

基本用法示例

1 简单的协程切换

function task1() {
    for ($i = 1; $i <= 3; $i++) {
        echo "任务1 - 步骤$i\n";
        Fiber::suspend($i);
    }
    return "任务1完成";
}
function task2() {
    for ($i = 1; $i <= 3; $i++) {
        echo "任务2 - 步骤$i\n";
        Fiber::suspend($i * 10);
    }
    return "任务2完成";
}
// 创建两个 Fiber
$fiber1 = new Fiber('task1');
$fiber2 = new Fiber('task2');
// 交替执行
$fiber1->start();
$fiber2->start();
while (!$fiber1->isTerminated() || !$fiber2->isTerminated()) {
    if (!$fiber1->isTerminated()) {
        echo "从任务1收到: " . $fiber1->resume() . "\n";
    }
    if (!$fiber2->isTerminated()) {
        echo "从任务2收到: " . $fiber2->resume() . "\n";
    }
}

2 数据传递

$fiber = new Fiber(function($param) {
    echo "开始执行,接收参数: $param\n";
    $value = Fiber::suspend("请求数据");
    echo "收到数据: $value\n";
    $value = Fiber::suspend("再次请求");
    echo "再次收到: $value\n";
    return "最终结果";
});
// 启动并传入初始参数
$result = $fiber->start("初始参数");
echo "第一次暂停: $result\n";
// 恢复并传入数据
$result = $fiber->resume("数据1");
echo "第二次暂停: $result\n";
$result = $fiber->resume("数据2");
echo "返回结果: $result\n";

实用场景示例

1 模拟异步 IO

class AsyncFileReader {
    public function readFiles(array $files) {
        $fibers = [];
        // 创建所有 Fiber
        foreach ($files as $index => $file) {
            $fibers[$index] = new Fiber(function($file) {
                // 模拟异步读取
                for ($i = 1; $i <= 100; $i++) {
                    if ($i % 25 === 0) {
                        Fiber::suspend($i / 25 * 25);
                    }
                    // 模拟读取数据
                    usleep(1000);
                }
                return file_get_contents($file);
            });
            $fibers[$index]->start($file);
        }
        // 收集结果
        $results = [];
        $completed = 0;
        $maxFiles = count($files);
        while ($completed < $maxFiles) {
            foreach ($fibers as $index => $fiber) {
                if (!$fiber->isTerminated()) {
                    $progress = $fiber->resume();
                    echo "文件{$index}: {$progress}%\n";
                    if ($fiber->isTerminated()) {
                        $results[$files[$index]] = $fiber->getReturn();
                        $completed++;
                    }
                }
            }
        }
        return $results;
    }
}

2 简单的任务调度器

class Scheduler {
    private array $tasks = [];
    public function add(callable $task) {
        $this->tasks[] = new Fiber($task);
    }
    public function run() {
        while (!empty($this->tasks)) {
            foreach ($this->tasks as $key => $fiber) {
                if ($fiber->isStarted() && $fiber->isTerminated()) {
                    unset($this->tasks[$key]);
                    continue;
                }
                if (!$fiber->isStarted()) {
                    $fiber->start();
                } else {
                    $fiber->resume();
                }
                // 如果任务已完成,移除
                if ($fiber->isTerminated()) {
                    unset($this->tasks[$key]);
                }
            }
        }
    }
}
// 使用调度器
$scheduler = new Scheduler();
$scheduler->add(function() {
    for ($i = 0; $i < 5; $i++) {
        echo "任务A: $i\n";
        Fiber::suspend();
    }
});
$scheduler->add(function() {
    for ($i = 0; $i < 3; $i++) {
        echo "任务B: $i\n";
        Fiber::suspend();
    }
});
$scheduler->run();

3 模拟并发请求

class ConcurrentHttpClient {
    public function request(array $urls) {
        $fibers = [];
        foreach ($urls as $index => $url) {
            $fibers[$index] = new Fiber(function($url) {
                // 模拟发送请求
                $ch = curl_init($url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                // 分段执行请求
                $result = '';
                for ($i = 0; $i < 10; $i++) {
                    usleep(100000); // 100ms
                    $result .= "chunk$i-";
                    if ($i < 9) {
                        Fiber::suspend();
                    }
                }
                curl_close($ch);
                return $result;
            });
            $fibers[$index]->start($url);
        }
        $results = [];
        foreach ($fibers as $index => $fiber) {
            while (!$fiber->isTerminated()) {
                $fiber->resume();
            }
            $results[$urls[$index]] = $fiber->getReturn();
        }
        return $results;
    }
}

重要方法和状态

$fiber = new Fiber(function() {
    echo "执行中\n";
    Fiber::suspend();
    echo "继续执行\n";
});
// 状态检查
echo $fiber->isStarted() ? "已启动\n" : "未启动\n";     // false
echo $fiber->isSuspended() ? "已暂停\n" : "未暂停\n";   // false
echo $fiber->isTerminated() ? "已终止\n" : "未终止\n";   // false
$fiber->start();
echo $fiber->isSuspended() ? "已暂停\n" : "未暂停\n";   // true
$fiber->resume();
echo $fiber->isTerminated() ? "已终止\n" : "未终止\n";   // true
// 获取返回值
$result = $fiber->getReturn();

异常处理

$fiber = new Fiber(function() {
    try {
        // 业务逻辑
        throw new Exception("自定义异常");
    } catch (Exception $e) {
        echo "在 Fiber 内捕获: " . $e->getMessage() . "\n";
        Fiber::suspend("异常处理中");
        throw $e; // 重新抛出
    }
});
try {
    $fiber->start();
    $fiber->resume();
} catch (Exception $e) {
    echo "在外部捕获: " . $e->getMessage() . "\n";
}
// 或者使用 throw() 方法
$fiber2 = new Fiber(function() {
    echo "执行中\n";
    Fiber::suspend("暂停");
    echo "继续执行\n";
});
$fiber2->start();
$fiber2->throw(new Exception("外部抛入异常"));

与生成器的对比

// 生成器方式
function generatorTask() {
    for ($i = 1; $i <= 5; $i++) {
        yield $i;  // 只能传递数据,不能接收外部数据
    }
}
// Fiber 方式
$fiberTask = new Fiber(function() {
    for ($i = 1; $i <= 5; $i++) {
        $input = Fiber::suspend($i);  // 可以双向传递数据
        echo "收到输入: $input\n";
    }
});
$fiberTask->start();
$fiberTask->resume("输入数据1");

注意事项

  1. 内存使用:每个 Fiber 都有独立的调用栈,不要创建过多
  2. 性能:Fiber 切换比线程切换快,但不是在真并行
  3. 使用场景:适合 IO 密集型任务,不适合 CPU 密集计算
  4. 限制:不能在 Fiber 中调用 exitdie

Fiber 提供了强大的协程能力,但实际使用时需要根据场景选择合适的异步方案,对于简单的并行需求,可以考虑使用 yield 生成器;对于复杂的异步逻辑,Fiber 会是更好的选择。

抱歉,评论功能暂时关闭!