0

0

PHP 中使用 Fiber 实现多 URL 并发非阻塞 fread 教程

心靈之曲

心靈之曲

发布时间:2026-03-13 23:29:01

|

862人浏览过

|

来源于php中文网

原创

PHP 中使用 Fiber 实现多 URL 并发非阻塞 fread 教程

本文详解如何利用 php fiber 实现多个 http 流的真正并发读取,避免串行等待;通过统一调度未终止 fiber、轮询式分片读取,达成类似协程的 i/o 多路复用效果。

本文详解如何利用 php fiber 实现多个 http 流的真正并发读取,避免串行等待;通过统一调度未终止 fiber、轮询式分片读取,达成类似协程的 i/o 多路复用效果。

在 PHP 8.1+ 中,Fiber 提供了用户态协作式调度能力,是构建轻量异步 I/O 的理想基础——但需注意:Fiber 本身不提供自动 I/O 调度,必须由开发者显式轮询与协调。你遇到的问题(google.com 完全读完才开始 twitter.com)根源在于:原代码对每个 Fiber 执行了“同步耗尽式循环”(while (!$fiber->isTerminated()) { $fiber->resume(); }),导致调度权被单个 Fiber 长期独占,丧失并发性。

要实现真正的交错读取(如先各读 100 字节,再循环),关键策略是:启动全部 Fiber 后,进入统一事件循环,每轮仅对每个活跃 Fiber 调用一次 resume(),让它们协同推进。这模拟了经典的“协作式多任务”模型。

以下是重构后的完整可运行示例:

<?php

function getFiberFromStream($stream, $url): Fiber {
    return new Fiber(function ($stream) use ($url): void {
        while (!feof($stream)) {
            echo "reading 100 bytes from $url" . PHP_EOL;
            $contents = fread($stream, 100);
            // 主动让出控制权,返回当前批次数据
            Fiber::suspend($contents);
        }
        // feof 触发后 fiber 自然终止
    });
}

function getContents(array $urls): array {
    $contents = [];
    $fibers = []; // 存储 [Fiber, 当前累积内容, stream] 三元组

    // ✅ 第一阶段:批量启动所有 Fiber(不等待任何完成)
    foreach ($urls as $key => $url) {
        $stream = fopen($url, 'r');
        if (!$stream) {
            throw new RuntimeException("Failed to open stream for $url");
        }
        stream_set_blocking($stream, false); // 关键:设为非阻塞!

        $fiber = getFiberFromStream($stream, $url);
        $initialContent = $fiber->start($stream); // 启动并获取首块数据

        $fibers[$key] = [$fiber, $initialContent ?? '', $stream];
    }

    // ✅ 第二阶段:统一轮询调度(核心!)
    $hasActive = true;
    while ($hasActive) {
        $hasActive = false;

        foreach ($fibers as $key => &$item) {
            [$fiber, $content, $stream] = $item;

            if (!$fiber->isTerminated()) {
                $hasActive = true;
                // 仅执行一次 resume,获取下一批数据
                try {
                    $nextChunk = $fiber->resume();
                    $item[1] = $content . ($nextChunk ?? '');
                } catch (Throwable $e) {
                    // Fiber 内部异常(如 fread 失败),标记为终止并记录错误
                    $item[1] = $content . "[ERROR: {$e->getMessage()}]";
                    $fiber = null; // 防止重复 resume
                }
            } else {
                // Fiber 已终止:关闭流并保存结果
                if ($stream && is_resource($stream)) {
                    fclose($stream);
                    $item[2] = null; // 清空引用,避免重复关闭
                }
                $contents[$urls[$key]] = $content;
                unset($fibers[$key]); // 清理已完成项,减少后续遍历开销
            }
        }

        // ⚠️ 重要:无活跃 Fiber 时需退出,否则死循环
        if (empty($fibers)) break;
    }

    return $contents;
}

// 使用示例(建议在支持 HTTPS 的环境中运行)
$urls = [
    'https://httpbin.org/delay/1', // 模拟慢响应
    'https://httpbin.org/delay/1',
    'https://httpbin.org/delay/1',
];

// 注意:实际生产中应添加超时、重试、错误熔断等健壮性逻辑
$result = getContents($urls);
var_dump(array_keys($result));

关键要点说明

  • 非阻塞流是前提:stream_set_blocking($stream, false) 不可省略。若流阻塞,fread() 将挂起整个 Fiber,破坏并发性。
  • 调度逻辑分离:启动(start)与执行(resume)严格解耦。所有 Fiber 必须先 start(),再统一 resume(),这是实现交错执行的基石。
  • 状态管理需显式:Fiber 无内置上下文存储,需用数组或对象维护 $content 和 $stream 等状态,避免闭包变量被覆盖。
  • 资源安全关闭:务必在 Fiber 终止后 fclose(),且通过置空 $item[2] 或 unset() 防止重复关闭导致警告。
  • 错误防御不可少:真实场景中,网络抖动、DNS 失败、SSL 错误均会导致 fread() 返回 false 或抛异常,应在 resume() 外层加 try/catch。

? 进阶提示:此模式是手动实现的“协程调度器”。若项目复杂度上升,建议迁移到成熟异步框架(如 SwooleReactPHP),它们已封装 epoll/kqueue 事件循环与自动流调度,大幅降低心智负担。

吐槽大师
吐槽大师

吐槽大师(Roast Master) - 终极 AI 吐槽生成器,适用于 Instagram,Facebook,Twitter,Threads 和 Linkedin

下载

立即学习PHP免费学习笔记(深入)”;

通过以上重构,输出将严格符合预期交错模式,真正释放 Fiber 在 I/O 密集型场景下的并发潜力。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
swoole为什么能常驻内存
swoole为什么能常驻内存

swoole常驻内存的特性:1. 事件驱动模型减少内存消耗;2. 协程并行执行任务占用更少内存;3. 协程池预分配协程消除创建开销;4. 静态变量保留状态减少内存分配;5. 共享内存跨协程共享数据降低内存开销。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

307

2024.04.10

while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

107

2023.09.25

fclose函数的用法
fclose函数的用法

fclose是一个C语言和C++中的标准库函数,用于关闭一个已经打开的文件,是文件操作中非常重要的一个函数,用于将文件流与底层文件系统分离,释放相关的资源。更多关于fclose函数的相关问题,详情请看本专题下面的文章。php中文网欢迎大家前来学习。

344

2023.11.30

go语言闭包相关教程大全
go语言闭包相关教程大全

本专题整合了go语言闭包相关数据,阅读专题下面的文章了解更多相关内容。

153

2025.07.29

http500解决方法
http500解决方法

http500解决方法有检查服务器日志、检查代码错误、检查服务器配置、检查文件和目录权限、检查资源不足、更新软件版本、重启服务器或寻求专业帮助等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

497

2023.11.09

http请求415错误怎么解决
http请求415错误怎么解决

解决方法:1、检查请求头中的Content-Type;2、检查请求体中的数据格式;3、使用适当的编码格式;4、使用适当的请求方法;5、检查服务器端的支持情况。更多http请求415错误怎么解决的相关内容,可以阅读下面的文章。

452

2023.11.14

HTTP 503错误解决方法
HTTP 503错误解决方法

HTTP 503错误表示服务器暂时无法处理请求。想了解更多http错误代码的相关内容,可以阅读本专题下面的文章。

3598

2024.03.12

http与https有哪些区别
http与https有哪些区别

http与https的区别:1、协议安全性;2、连接方式;3、证书管理;4、连接状态;5、端口号;6、资源消耗;7、兼容性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2917

2024.08.16

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 13.4万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.3万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号