用 yield 替代 return array 可将内存从全量驻留降为单条持有,百万数据下内存占用从几十 MB 降至几乎为 0;yield 是同步迭代协议,非协程,不可与 async/await 混用,且生成器函数必须声明 Generator 返回类型。

直接说结论:用 yield 替代 return array,不是“写法更酷”,而是把内存从“全量驻留”压到“单条持有”。百万级数据下,内存占用能从几十 MB 降到几乎为 0。
为什么 foreach 大数组会爆内存
PHP 普通函数返回数组时,range(0, 1000000) 这类操作会在调用瞬间分配完整内存块——所有 100 万个整数被同时存进 RAM。哪怕只是遍历、不做修改,这个数组也全程不释放。
常见错误现象包括:Fatal error: Allowed memory size of XXX bytes exhausted;或者在 CLI 下跑脚本突然中断,在 Web 环境下则可能触发 500 或超时。
关键点在于:你根本不需要随机访问(比如 $arr[500000]),却被迫为全部元素买单。
立即学习“PHP免费学习笔记(深入)”;
yield 函数必须声明返回 Generator 类型
PHP 7.1+ 强制要求生成器函数标注返回类型,否则会报 TypeError。这不是可选项,是运行前提。
-
function getUsers(): Generator { ... }✅ 正确 -
function getUsers() { ... }❌ PHP 8.1+ 会警告,PHP 8.2+ 默认报错 -
function getUsers(): iterable { ... }⚠️ 兼容性宽泛但失去 IDE 提示和静态分析支持
参数本身无特殊限制,但注意:生成器函数内不能使用 return $value(除 return; 表示结束),否则会抛出 Exception: A generator must not return a value。
yield 不是协程,别混用 yield 和 async/await
很多人看到“边算边交”就联想到协程,但 yield 本身是同步迭代协议的一部分,它不切换线程、不挂起 I/O、也不自动让出 CPU。它只做一件事:暂停函数执行,交出一个值,等下次 foreach 或 Iterator::next() 再恢复。
典型误用场景:
- 在
yield后面直接写file_get_contents()或数据库查询——这仍是同步阻塞,不会提速,只是省内存 - 期望
yield能自动并发处理多个文件——不行,得配合Swoole\Coroutine或ext-parallel - 把生成器当成普通数组传给
array_map()或count()——会报Warning: array_map() expects parameter 1 to be a valid callback,因为 Generator 不是数组
真实文件读取中 yield 的写法陷阱
读大文本文件时,最常写的 fgets() + yield 看似简单,但容易漏掉资源清理和编码兼容问题。
正确写法要点:
- 必须在
yield后、fclose()前加try...finally或显式检查feof(),否则文件句柄可能泄漏 -
fgets()返回含换行符的字符串,若后续要trim(),别在yield前做——那会提前加载并复制字符串,削弱 yield 效果 - 如果文件是 UTF-8 BOM 开头,
fgets()会把 BOM 当作内容 yield 出来,需在消费端处理,或改用stream_get_line()配合mb_detect_encoding()
一个安全底线示例:
function readLines(string $path): Generator
{
$handle = @fopen($path, 'r');
if (!$handle) {
throw new RuntimeException("Cannot open file: $path");
}
try {
while (($line = fgets($handle)) !== false) {
yield $line;
}
} finally {
fclose($handle);
}
}
真正难的从来不是语法,而是意识到:只要数据流是线性的、单向的、无需回溯,你就该本能地怀疑——这里是不是该用 yield?而不是先写个大数组再祈祷不崩。



















