本文目录导读:

在 PHP 项目中,“纤程”(Fibers)和“并发”是两个密切相关但概念不同的主题,特别是自 PHP 8.1 引入纤程以来,PHP 的并发模型有了很大的变化。
下面我来系统解析这两个概念,以及它们在 PHP 项目中的实际应用。
核心概念区分
| 概念 | 本质 | 特点 |
|---|---|---|
| 纤程(Fiber) | 一种轻量级的协程,在单线程内实现协作式多任务 | 手动挂起/恢复,非抢占式 |
| 并发(Concurrency) | 多个任务在同一时间段内交替执行 | 逻辑上的同时执行,不一定是物理并行 |
| 并行(Parallelism) | 多个任务在同一时刻真正同时执行 | 依赖多核 CPU |
关键点: PHP 纤程本质上是在一个 PHP 进程中实现单线程并发,而非并行。
PHP 纤程详解
1 什么是 Fiber?
Fiber 是 PHP 8.1 引入的底层协程实现,它允许你在代码中手动暂停执行(yield),然后稍后恢复执行,而不需要使用回调函数或生成器。
2 基本用法
<?php
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first suspend');
echo "Resumed with value: $value\n";
});
// 启动纤程
$value = $fiber->start();
echo "Got from fiber: $value\n"; // 输出: Got from fiber: first suspend
// 恢复纤程,并传递值
$fiber->resume('second value'); // 输出: Resumed with value: second value
3 纤程的核心特性
- 非抢占式:纤程必须主动调用
Fiber::suspend()才能暂停,不会被系统强制切换 - 单线程内:所有纤程共享同一个 PHP 进程和线程
- 栈式协程:与生成器不同,纤程有自己的完整调用栈
- 可嵌套:纤程内部可以再创建纤程
PHP 中的并发模型演进
1 传统模型(PHP 8.0 及之前)
// 串行执行 $result1 = someApiCall1(); // 100ms $result2 = someApiCall2(); // 100ms // 总耗时: 200ms
2 使用纤程实现并发(PHP 8.1+)
$fiber1 = new Fiber(function () {
$result = someApiCall(); // 100ms
Fiber::suspend($result);
return $result;
});
$fiber2 = new Fiber(function () {
$result = someApiCall(); // 100ms
Fiber::suspend($result);
return $result;
});
$fiber1->start();
$fiber2->start();
$result1 = $fiber1->resume();
$result2 = $fiber2->resume();
// 理想总耗时: ~100ms(I/O 可并行)
3 与 ReactPHP / Amp 等事件循环库结合
单独使用纤程并不能实现真正的并发 I/O,必须配合事件循环和非阻塞 I/O。
// 使用 Amp + Fiber 实现真正的并发
\Amp\Loop::run(function () {
$promises = [
asyncHttpCall('https://api.example.com/1'),
asyncHttpCall('https://api.example.com/2'),
];
$results = yield \Amp\Promise\all($promises);
});
纤程的应用场景
1 最适合的场景
-
I/O 密集型并发:
- 多路 HTTP 请求
- 数据库查询并发
- 文件读写操作
- 微服务调用编排
-
任务调度与控制:
- 生成器替代(更清晰的语法)
- 状态机实现
- 任务暂停/恢复
-
异步流程控制:
- 避免回调地狱
- 替代复杂的状态管理
2 不合适的场景
- CPU 密集型任务(计算、加密、图像处理)
- 需要真正并行的场景(请使用多进程/多线程)
- 已有的同步代码库(需要大量改造)
实战示例:并发 HTTP 请求
1 使用纯纤程(无法真正并发 I/O)
// 这个例子实际上还是串行的,因为 file_get_contents 是阻塞的
$fiber1 = new Fiber(function () {
return file_get_contents('https://api.example.com/1');
});
$fiber2 = new Fiber(function () {
return file_get_contents('https://api.example.com/2');
});
$fiber1->start();
$fiber2->start();
$result1 = $fiber1->resume(); // 这里才会真正执行
$result2 = $fiber2->resume();
注意: 如果使用阻塞 I/O 函数,纤程无法带来并发收益。
2 真正的并发:Fiber + 非阻塞 I/O
// 使用 ext-curl 的多句柄机制 + Fiber
class FiberHttpClient {
private array $handles = [];
public function request(string $url): Fiber {
return new Fiber(function () use ($url) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, false);
$this->handles[spl_object_id($ch)] = $ch;
Fiber::suspend($ch);
return curl_multi_getcontent($ch);
});
}
public function execute(): array {
$mh = curl_multi_init();
foreach ($this->handles as $id => $ch) {
curl_multi_add_handle($mh, $ch);
}
do {
$status = curl_multi_exec($mh, $active);
// 这里可以让其他纤程运行
} while ($active);
// 恢复所有纤程
$results = [];
foreach ($this->handles as $id => $ch) {
$results[$id] = curl_multi_getcontent($ch);
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $results;
}
}
最佳实践与常见陷阱
1 最佳实践
- 合理使用:纤程适合 I/O 密集型,不适合 CPU 密集型
- 结合事件循环:单独纤程无法实现并发,需配合非阻塞 I/O
- 避免共享状态:纤程间共享变量需要谨慎,考虑使用
ThreadSafe机制 - 错误处理:纤程内的异常会冒泡到调用者
2 常见陷阱
| 陷阱 | 原因 | 解决方案 |
|---|---|---|
| 阻塞导致无并发 | 使用了阻塞 I/O 函数 | 使用非阻塞 I/O 库(如 amphp) |
| 内存泄漏 | 纤程长期未销毁 | 确保纤程最终完成 or 手动 destroy() |
| 死锁 | 纤程间循环等待 | 设计良好的任务调度逻辑 |
| 性能不升反降 | 创建过多纤程 | 控制并发数(类似协程池) |
总结与展望
1 要不要在生产中使用?
- PHP 8.1+ 项目:推荐尝试,特别是处理 I/O 密集型任务
- 现有项目:渐进式引入,从一个小模块开始
- 新项目:可以考虑结合
amphp、reactphp等框架
2 与异步框架的关系
Fiber(底层)
→ amphp (基于Fiber的异步框架)
→ 实际项目应用
3 未来趋势
- PHP 8.x 版本将进一步优化 Fiber 性能
- 更多框架将原生支持 Fiber(Laravel、Symfony 已在探索)
- 数据库驱动、HTTP 客户端将提供非阻塞版本
建议: 如果你是首次接触,可以先从简单的 I/O 并发场景开始,对于复杂的生产项目,建议使用成熟的异步框架(如 amphp)而非直接操作 Fiber,这样可以享受更好的错误处理、超时控制和资源管理。