本文目录导读:

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");
注意事项
- 内存使用:每个 Fiber 都有独立的调用栈,不要创建过多
- 性能:Fiber 切换比线程切换快,但不是在真并行
- 使用场景:适合 IO 密集型任务,不适合 CPU 密集计算
- 限制:不能在 Fiber 中调用
exit或die
Fiber 提供了强大的协程能力,但实际使用时需要根据场景选择合适的异步方案,对于简单的并行需求,可以考虑使用 yield 生成器;对于复杂的异步逻辑,Fiber 会是更好的选择。