Hyperf 中 file_get_contents 仍阻塞,因其底层为同步系统调用,不感知协程调度;即使开启 SWOOLE_HOOK_ALL,Swoole 也不接管普通文件的 read()/write() 操作,必须改用 Co::readFile 等协程 I/O 接口。

Hyperf 中 file_get_contents 为什么还是阻塞的?
哪怕你在 Hyperf 的协程环境中直接调用 file_get_contents,它依然会阻塞当前协程 —— 因为 PHP 原生文件函数(包括 fopen、file_get_contents、file_put_contents)底层走的是同步系统调用,不感知协程调度。
常见现象:在 Coroutine::create 或控制器里调用 file_get_contents('/big.log'),整个协程卡住,其他并发请求也被拖慢。
- 别指望加
Co::set(['enable_coroutine' => true])就能让原生函数变协程安全 - Hyperf 默认未自动 Hook 文件函数(仅默认 Hook 了 socket、stream、curl 等)
- 即使开启
hook_flags = SWOOLE_HOOK_ALL,PHP 的标准文件 I/O 仍不会被协程化(Swoole 不接管read()/write()对普通文件描述符的操作)
真正可用的协程文件读写方式有哪些?
必须绕过原生文件函数,改用 Swoole 提供的协程 I/O 接口或 Hyperf 封装的异步适配层。
-
Co::readFile和Co::writeFile:Swoole 原生协程文件操作,支持超时、自动分块,推荐优先使用 -
Hyperf\Utils\Coordinator\Timer+Process:大文件场景下,用子进程处理,避免协程长时间占用 -
Swoole\Coroutine\Channel配合Co::exec调用cat/dd:极端场景兜底,但需权衡安全性与开销
示例(安全读取):
use Swoole\Coroutine as Co;
$result = Co::readFile('/tmp/data.json', 5); // 第二个参数是超时秒数
if ($result === false) {
throw new RuntimeException('readFile timeout or failed: ' . Co::getSocketErrno());
}
$data = json_decode($result, true);
Co::readFile 和 Co::writeFile 的关键限制
它们不是万能的,实际用时得看清边界条件。
- 只支持本地文件路径,不支持
phar://、zip://、http://等封装协议 - 不支持偏移读写(如只读第 1024–2048 字节),整文件加载到内存 —— 大于内存限制会直接失败
-
Co::writeFile是覆盖写,没有FILE_APPEND选项;追加需先Co::readFile再拼接后重写 - 权限错误时返回
false,但Co::getSocketErrno()可能返回0(非 socket 错误),建议同时检查is_file()和is_readable()
大文件或流式处理该怎么协程化?
当文件超过几 MB,或需要边读边解析(比如 CSV 流式导入),Co::readFile 就不合适了。这时得手动用 Co::open + Co::read 模拟流。
-
Co::open返回的是协程化的resource,但只能用于普通文件(不能是目录、设备文件) - 每次
Co::read最多读 8MB,超出部分需循环;记得用feof()判断结束,而不是依赖返回空字符串 - 务必手动
Co::close,否则 fd 泄漏(Hyperf 不自动回收协程内打开的文件句柄) - 若涉及编码转换(如 GBK → UTF-8),别在协程循环里调用
mb_convert_encoding—— 它是同步 CPU 密集型操作,考虑丢给Parallel\Worker或ProcessPool
一句话收尾:协程文件 I/O 的核心不是“怎么快”,而是“别让一个文件把整个协程池拖垮”。路径、大小、权限、关闭时机,四者漏一,就可能在线上静默失败。


















