
本文介绍如何避免php中非阻塞stdin读取导致cpu 100%占用的问题,通过引入微秒级休眠(time_nanosleep)实现低开销轮询,并提供完整、健壮的键盘按键监听方案。
在命令行PHP应用中,若需实时响应用户按键(如制作简易交互菜单或游戏),常采用 fgets() 配合非阻塞流的方式监听 STDIN。但直接使用 stream_set_blocking($stdin, false) 后在 while(true) 中高频调用 fgets(),会导致进程持续空转——每次 fgets() 立即返回 false(无数据),CPU无实际工作却满负荷运转,这是典型的忙等待(busy-waiting)反模式。
正确的做法是:在每次轮询失败后主动让出CPU时间片,避免无效竞争。PHP 提供了高精度睡眠函数 time_nanosleep(),可指定纳秒级休眠(如 5 * 10⁶ 纳秒 = 5 毫秒),既保证响应及时性(人类感知延迟
$stdin = fopen("php://stdin", "r");
if (!$stdin) {
die("无法打开标准输入流\n");
}
// 设置终端为cbreak模式:按键立即生效,不回显
system("stty cbreak -echo 2>/dev/null");
// 关闭流阻塞,启用非阻塞读取
stream_set_blocking($stdin, false);
$key = '';
$t = 1000000; // 1 微秒 = 1000 纳秒,故 10⁶ 纳秒 = 1 毫秒
while (true) {
$line = fgets($stdin);
if ($line !== false) {
$key = strtoupper(trim($line));
break;
}
// 每次无输入时休眠 5 毫秒,显著降低CPU负载
time_nanosleep(0, 5 * $t);
}
// 恢复终端默认行为(重要!防止退出后终端异常)
system("stty -cbreak echo 2>/dev/null");
stream_set_blocking($stdin, true);
fclose($stdin);
echo "捕获到按键: {$key}\n";
⚠️ 关键注意事项:
- 必须恢复终端设置:stty cbreak -echo 会禁用回显和行缓冲,程序退出前务必执行 stty -cbreak echo,否则用户后续终端输入将不可见或需手动重置(如运行 reset 命令);
- 错误处理不可省略:fopen() 和 fgets() 可能失败,应加入基础判空;
- 替代方案参考:对更复杂交互场景,可考虑 ext-posix + pcntl_signal() 结合 select() 实现事件驱动,或使用 readline() 扩展(需启用)获取更安全的行输入支持;
- 跨平台提醒:stty 是Unix/Linux/macOS命令,Windows下需改用 mode con 或依赖 ext-pcntl/第三方库(如 symfony/console 的交互组件)。
综上,仅增加 time_nanosleep() 这一行休眠调用,即可将CPU占用从100%降至忽略不计,同时保持毫秒级响应能力——这是命令行PHP交互开发中简单、有效且必备的优化实践。
立即学习“PHP免费学习笔记(深入)”;











