2026/7/26 1:20:52

别再混淆 PHP. 中纤程 Fibers 和协程 Coroutines 了 一文搞懂它们的区别

别再混淆 PHP. 中纤程 Fibers 和协程 Coroutines 了 一文搞懂它们的区别 别再混淆 PHP 中纤程 Fibers 和协程 Coroutines 了一文搞懂它们的区别在 PHP 的多任务编程世界里纤程Fibers和协程Coroutines是两个常被混淆的概念。虽然它们都涉及“暂停”和“恢复”执行但底层机制和适用场景截然不同。本文将从实战出发用代码演示带你彻底搞清它们的区别。## 什么是协程PHP 中的生成器就是协程PHP 本身没有原生协程关键字如 Go 的go或 Python 的async def但它的生成器Generator实际上是一种协程实现。生成器通过yield关键字实现函数暂停和恢复从而实现协作式多任务。### 实战示例1用生成器实现简单协程php?php/** * 模拟读取多个文件的任务协程版本 * 每个协程在 yield 处暂停主循环控制调度 */function readFileTask(string $filename): Generator{ $handle fopen($filename, r); if (!$handle) { yield 无法打开文件: $filename; return; } $content ; while (!feof($handle)) { $chunk fread($handle, 1024); $content . $chunk; // 每读取1KB就暂停让其他协程运行 yield 读取中: . strlen($content) . 字节; } fclose($handle); yield 完成: $filename (总大小: . strlen($content) . 字节);}// 创建两个协程任务$task1 readFileTask(data1.txt);$task2 readFileTask(data2.txt);// 手动调度协程协作式多任务$tasks [$task1, $task2];$running true;while ($running) { $running false; foreach ($tasks as $key $task) { if ($task-valid()) { $running true; echo 任务$key: . $task-current() . PHP_EOL; $task-next(); // 继续执行到下一个 yield } }}输出示例任务0: 读取中: 1024 字节任务1: 读取中: 1024 字节任务0: 读取中: 2048 字节任务1: 读取中: 2048 字节...任务0: 完成: data1.txt (总大小: 5120 字节)任务1: 完成: data2.txt (总大小: 3072 字节)关键点- 协程通过yield主动让出 CPU 控制权- 调度由外部循环手动控制协作式- 每个生成器实例代表一个协程状态- 协程之间没有自动切换需要显式调用next()## 什么是纤程PHP 8.1 引入的原生控制流纤程Fiber是 PHP 8.1 引入的全新特性它允许在调用栈中任意位置暂停和恢复执行而不仅仅是函数的顶部。纤程更像是一个轻量级的线程由 PHP 运行时管理调度。### 实战示例2用纤程实现异步 HTTP 请求模拟php?php/** * 模拟异步 HTTP 请求纤程版本 * 纤程可以在函数调用栈深处暂停然后由调度器恢复 */function httpRequest(string $url, int $delay 2): string{ // 模拟网络延迟 echo 开始请求: $url (延迟 {$delay}秒) . PHP_EOL; // 在函数内部暂停纤程而不是返回 Fiber::suspend($delay); // 恢复后继续执行 echo 完成请求: $url . PHP_EOL; return 响应数据来自: $url;}// 创建两个纤程$fiber1 new Fiber(function () { $result httpRequest(https://api.example.com/users, 2); echo 第一个纤程结果: $result . PHP_EOL;});$fiber2 new Fiber(function () { $result httpRequest(https://api.example.com/posts, 3); echo 第二个纤程结果: $result . PHP_EOL;});// 启动所有纤程开始执行直到第一个 suspendecho 启动纤程 . PHP_EOL;$fiber1-start();$fiber2-start();// 模拟事件循环当纤程挂起时我们可以做其他事echo 纤程挂起主程序继续执行其他任务 . PHP_EOL;sleep(1); // 模拟处理其他任务// 恢复纤程1假设它的延迟已结束echo 恢复纤程1 . PHP_EOL;$fiber1-resume();// 恢复纤程2echo 恢复纤程2 . PHP_EOL;$fiber2-resume();echo 所有纤程完成 . PHP_EOL;输出示例 启动纤程 开始请求: https://api.example.com/users (延迟 2秒)开始请求: https://api.example.com/posts (延迟 3秒) 纤程挂起主程序继续执行其他任务 恢复纤程1 完成请求: https://api.example.com/users第一个纤程结果: 响应数据来自: https://api.example.com/users 恢复纤程2 完成请求: https://api.example.com/posts第二个纤程结果: 响应数据来自: https://api.example.com/posts 所有纤程完成 关键点- 纤程可以在任意深度嵌套函数调用中暂停而协程只能在生成器函数顶层 yield- 纤程有独立的调用栈可以保存完整的执行上下文- 调度由Fiber::suspend()和Fiber::resume()控制但可以集成到事件循环中- PHP 运行时负责管理纤程的栈内存## 核心区别对比| 特性 | 协程生成器 | 纤程 ||------|---------------|------||暂停位置| 只能在生成器函数顶层 yield | 可以在任意嵌套函数中 suspend ||调用栈| 无独立调用栈暂停时返回上层 | 有独立完整调用栈 ||内存管理| 使用 PHP 普通栈帧 | 需要分配独立栈空间默认 1MB ||性能开销| 极低无额外内存分配 | 较高需分配栈内存 ||调度方式| 完全手动协作式 | 手动 可集成事件循环 ||引入版本| PHP 5.5 | PHP 8.1 ||典型用途| 迭代器、简单异步任务 | 复杂异步 I/O、并发任务调度 |## 何时使用协程 vs 纤程使用协程生成器的场景- 实现简单的迭代器如读取大文件逐行处理- 不需要嵌套调用的简单任务切换- 内存敏感的环境纤程的栈分配可能过大- 向后兼容旧版 PHP使用纤程的场景- 需要深度嵌套的异步操作如多层回调或依赖注入- 构建异步框架如 Amp、ReactPHP 的底层调度- 需要保存完整调用栈以便调试- 处理大量并发 I/O 操作如 WebSocket 服务器## 总结协程和纤程的核心区别在于调用栈管理。协程生成器是轻量级的协作式多任务它通过yield在函数顶层暂停不保存调用栈适合简单的任务切换。纤程则是更强大的原语它拥有独立的调用栈可以在任意深度暂停和恢复适合构建复杂的异步系统。理解这种区别有助于你在实际项目中选择正确的工具对于简单的迭代和任务调度生成器足够对于需要完整异步能力的复杂系统纤程将提供更高的灵活性和控制力。记住纤程不是协程的替代品而是 PHP 多任务编程的另一个强大武器。