
php 使用非阻塞 `fgets()` 监听标准输入时会陷入空转循环,导致 cpu 持续 100% 占用;通过在每次循环中引入微秒级休眠(如 `time_nanosleep(0, 5000000)`)可显著降低资源消耗,同时保持响应灵敏度。
在命令行 PHP 应用中,实现“按任意键继续”或实时按键捕获时,开发者常采用非阻塞 I/O 配合轮询方式检测 STDIN 数据。但如原始代码所示:
$stdin = fopen("php://stdin", "r");
stream_set_blocking($stdin, false);
system("stty cbreak -echo");
while (true) {
if ($keypress = strtoupper(fgets($stdin))) {
break;
}
}
stream_set_blocking($stdin, true);该逻辑存在严重性能缺陷:fgets() 在非阻塞模式下无输入时立即返回 false 或空字符串,循环体不设任何延迟,形成紧密忙等待(busy-waiting),使单核 CPU 占用率长期维持在接近 100%,不仅浪费系统资源,还可能干扰同机其他进程。
✅ 推荐优化方案:插入轻量级休眠
使用 time_nanosleep(0, $nanoseconds) 在每次轮询失败后暂停极短时间(例如 5 毫秒 = 5,000,000 纳秒),既避免忙等待,又不影响用户体验(人类无法感知 5ms 延迟)。修正后的健壮写法如下:
立即学习“PHP免费学习笔记(深入)”;
$stdin = fopen("php://stdin", "r");
if (!$stdin) {
die("无法打开 STDIN\n");
}
stream_set_blocking($stdin, false);
system("stty cbreak -echo"); // 启用字符即时读取,禁用回显
$t = 1000000; // 1 microsecond = 1000 nanoseconds → 此处为单位换算基准
$keypress = '';
while (true) {
$line = fgets($stdin);
if ($line !== false && trim($line) !== '') {
$keypress = strtoupper(trim($line));
break;
}
// 每次未读到数据时休眠 5ms,大幅降低 CPU 负载
time_nanosleep(0, 5 * $t * 1000); // 注意:time_nanosleep 第二参数单位为纳秒 → 5ms = 5_000_000 ns
}
// 恢复终端默认行为(重要!)
system("stty -cbreak echo");
stream_set_blocking($stdin, true);
fclose($stdin);
echo "捕获按键: {$keypress}\n";⚠️ 关键注意事项:
- time_nanosleep() 在 Windows 上不可用,请确保脚本仅运行于类 Unix 环境(Linux/macOS);Windows 用户可改用 usleep(5000)(单位为微秒),效果等效;
- 务必在退出前调用 stty -cbreak echo 恢复终端回显与行缓冲,否则可能导致后续命令行操作异常(如输入不显示、回车无响应);
- fgets() 默认读取至换行符,若需单字符响应(如方向键、ESC),应结合 fgetc() + 终端原始模式(stty raw -echo)并处理 ANSI 转义序列;
- 对于高可靠性 CLI 工具,建议封装为函数并增加超时机制(如 microtime(true) 计时),防止无限等待。
综上,微秒级休眠是平衡响应性与资源效率的黄金解法——它让轮询从“暴力扫描”转变为“智能守候”,是 PHP 命令行交互开发中的必备实践。











