
本文介绍如何解决 php 使用非阻塞 `fgets()` 监听 stdin 时导致 cpu 占用率飙升至 100% 的问题,并提供基于 `time_nanosleep()` 的轻量级轮询方案及更优的替代实践。
在命令行 PHP 应用中,常需实时响应用户按键(如菜单导航、交互式工具),开发者常采用 fopen("php://stdin") 配合 stream_set_blocking(false) 实现非阻塞读取。但原始写法存在严重性能缺陷:
$stdin = fopen("php://stdin", "r");
stream_set_blocking($stdin, false);
system("stty cbreak -echo");
while (true) {
if ($keypress = strtoupper(fgets($stdin))) {
break;
}
}该循环会以极高速度反复调用 fgets() —— 当输入缓冲区为空时,fgets() 立即返回 false,CPU 在无休止的“检查→失败→再检查”中满载运行,造成 100% 占用,既耗电又影响系统响应。
✅ 推荐修复方案:引入微秒级休眠
通过 time_nanosleep(0, N) 在每次空读之后暂停指定纳秒数(如 5 毫秒 = 5,000,000 纳秒),可将 CPU 占用降至接近 0%,同时保持毫秒级响应灵敏度:
$stdin = fopen("php://stdin", "r");
stream_set_blocking($stdin, false);
system("stty cbreak -echo");
$t = 1000000; // 1 微秒 = 1000 纳秒 → 此处单位为纳秒
while (!($keypress = strtoupper(trim(fgets($stdin))))) {
time_nanosleep(0, 5 * $t); // 休眠 5 毫秒
}
// 恢复终端设置(关键!)
system("stty -cbreak echo");
stream_set_blocking($stdin, true);
fclose($stdin);⚠️ 注意事项:
- 必须调用 trim() 清除换行符,避免误判空行;
- stty cbreak -echo 关闭回显并启用单字符输入,退出前务必恢复(如 stty -cbreak echo),否则终端可能异常;
- time_nanosleep() 在 Windows 下不可用,生产环境建议封装兼容逻辑或改用 usleep(5000)(精度略低但跨平台);
- 若需更高可靠性,可结合 stream_select() 实现真正的 I/O 多路复用(需 PHP ≥ 7.4,且仅限 Unix-like 系统):
$read = [$stdin];
$write = $except = [];
if (stream_select($read, $write, $except, 0, 5000) > 0) {
$keypress = strtoupper(trim(fgets($stdin)));
}? 总结:非阻塞轮询必须配合适当延时,time_nanosleep() 是平衡响应性与资源消耗的简洁解法;长期项目建议抽象为可配置的 KeyReader 类,并增加超时、信号中断等健壮性处理。
立即学习“PHP免费学习笔记(深入)”;











