在高并发或批处理场景里,我们常常不希望某些后台任务“吃满”CPU,影响对外服务的响应。操作系统已经提供了调整当前进程的优先级;若要控制子进程,可以在fork 的子进程里调用它,或结合 proc_open() 与 posix_setpriority() 针对特定 PID 调整。本文给出实战范式、坑点与排障建议。
小抄:在类 Unix 系统上,nice 取值范围通常是 -20(最高优先级)到 19(最低),默认是 0。
普通用户只能增大 niceness(降低优先级),例如从 0 → 10。
将优先级提高到负值(如 -5)通常需要 root 能力或 CAP_SYS_NICE。
当你用 pcntl_fork() 派生子进程时,在子进程调用 proc_nice() 最直接。示例:父进程负责接活,子进程做重活但降优先级,避免拖慢整体。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">declare</span></span><span>(strict_types=</span><span><span class="hljs-number">1</span></span><span>);
</span><span><span class="hljs-comment">// 确保扩展已启用:pcntl、posix(可选)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">function_exists</span></span><span>(</span><span><span class="hljs-string">'pcntl_fork'</span></span><span>)) {
</span><span><span class="hljs-title function_ invoke__">fwrite</span></span><span>(STDERR, </span><span><span class="hljs-string">"pcntl 未启用,无法演示\n"</span></span><span>);
</span><span><span class="hljs-keyword">exit</span></span><span>(</span><span><span class="hljs-number">1</span></span><span>);
}
</span><span><span class="hljs-variable">$pid</span></span><span> = </span><span><span class="hljs-title function_ invoke__">pcntl_fork</span></span><span>();
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$pid</span></span><span> === -</span><span><span class="hljs-number">1</span></span><span>) {
</span><span><span class="hljs-keyword">throw</span></span><span> </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-built_in">RuntimeException</span></span><span>(</span><span><span class="hljs-string">'fork 失败'</span></span><span>);
}
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$pid</span></span><span> === </span><span><span class="hljs-number">0</span></span><span>) {
</span><span><span class="hljs-comment">// --- 子进程 ---</span></span><span>
</span><span><span class="hljs-comment">// 将 niceness 从默认 0 调整为 10(更“友好”,更低优先级)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">proc_nice</span></span><span>(</span><span><span class="hljs-number">10</span></span><span>)) {
</span><span><span class="hljs-comment">// 失败通常意味着权限不足或平台不支持</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fwrite</span></span><span>(STDERR, </span><span><span class="hljs-string">"[child] proc_nice 失败\n"</span></span><span>);
}
</span><span><span class="hljs-comment">// 模拟 CPU 密集型工作</span></span><span>
</span><span><span class="hljs-variable">$sum</span></span><span> = </span><span><span class="hljs-number">0</span></span><span>;
</span><span><span class="hljs-keyword">for</span></span><span> (</span><span><span class="hljs-variable">$i</span></span><span> = </span><span><span class="hljs-number">0</span></span><span>; </span><span><span class="hljs-variable">$i</span></span><span> < </span><span><span class="hljs-number">5_000_000</span></span><span>; </span><span><span class="hljs-variable">$i</span></span><span>++) {
</span><span><span class="hljs-variable">$sum</span></span><span> += </span><span><span class="hljs-variable">$i</span></span><span>;
}
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"[child] done, sum=<span class="hljs-subst">{$sum}</span></span></span><span>\n";
</span><span><span class="hljs-keyword">exit</span></span><span>(</span><span><span class="hljs-number">0</span></span><span>);
} </span><span><span class="hljs-keyword">else</span></span><span> {
</span><span><span class="hljs-comment">// --- 父进程 ---</span></span><span>
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"[parent] fork 出子进程 PID=<span class="hljs-subst">{$pid}</span></span></span><span>\n";
</span><span><span class="hljs-comment">// 父进程继续高优先级工作(例如响应 HTTP 请求)</span></span><span>
</span><span><span class="hljs-comment">// 等待子进程结束(生产环境可选择不阻塞,或用信号/回调)</span></span><span>
</span><span><span class="hljs-title function_ invoke__">pcntl_wait</span></span><span>(</span><span><span class="hljs-variable">$status</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"[parent] child exit code = "</span></span><span> . </span><span><span class="hljs-title function_ invoke__">pcntl_wexitstatus</span></span><span>(</span><span><span class="hljs-variable">$status</span></span><span>) . </span><span><span class="hljs-string">"\n"</span></span><span>;
}
</span></span>
要点
proc_nice() 影响的是当前进程,因此要在子进程代码路径里调用。
调整为正值(如 10)通常不需要特权;设置负值可能失败。
如果只是“让子任务别抢资源”,把它设为 10~15 往往已经足够。
若你不是用 fork,而是通过 proc_open() 启动其他命令(如 ffmpeg、convert、php 自己跑 worker),你需要拿到 PID 再调优先级。proc_get_status() 能取到子进程 PID;随后用 posix_setpriority()(POSIX 扩展)对该 PID 操作更直观。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">declare</span></span><span>(strict_types=</span><span><span class="hljs-number">1</span></span><span>);
</span><span><span class="hljs-variable">$descriptorSpec</span></span><span> = [
</span><span><span class="hljs-number">0</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'r'</span></span><span>],
</span><span><span class="hljs-number">1</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'w'</span></span><span>],
</span><span><span class="hljs-number">2</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'w'</span></span><span>],
];
</span><span><span class="hljs-comment">// 举例:跑一个 CPU 密集的 PHP 子脚本</span></span><span>
</span><span><span class="hljs-variable">$process</span></span><span> = </span><span><span class="hljs-title function_ invoke__">proc_open</span></span><span>(</span><span><span class="hljs-string">'php -r "usleep(3000000);"'</span></span><span>, </span><span><span class="hljs-variable">$descriptorSpec</span></span><span>, </span><span><span class="hljs-variable">$pipes</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-title function_ invoke__">is_resource</span></span><span>(</span><span><span class="hljs-variable">$process</span></span><span>)) {
</span><span><span class="hljs-keyword">throw</span></span><span> </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-built_in">RuntimeException</span></span><span>(</span><span><span class="hljs-string">'proc_open 启动失败'</span></span><span>);
}
</span><span><span class="hljs-variable">$status</span></span><span> = </span><span><span class="hljs-title function_ invoke__">proc_get_status</span></span><span>(</span><span><span class="hljs-variable">$process</span></span><span>);
</span><span><span class="hljs-variable">$pid</span></span><span> = </span><span><span class="hljs-variable">$status</span></span><span>[</span><span><span class="hljs-string">'pid'</span></span><span>] ?? </span><span><span class="hljs-literal">null</span></span><span>;
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"child pid = <span class="hljs-subst">{$pid}</span></span></span><span>\n";
</span><span><span class="hljs-comment">// POSIX 方式:直接对 PID 设置 nice(范围 -20~19)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">function_exists</span></span><span>(</span><span><span class="hljs-string">'posix_setpriority'</span></span><span>) && </span><span><span class="hljs-variable">$pid</span></span><span>) {
</span><span><span class="hljs-comment">// 将子进程调为 10(更低优先级)</span></span><span>
</span><span><span class="hljs-variable">$ok</span></span><span> = @</span><span><span class="hljs-title function_ invoke__">posix_setpriority</span></span><span>(</span><span><span class="hljs-number">10</span></span><span>, </span><span><span class="hljs-variable">$pid</span></span><span>, POSIX_PRIO_PROCESS);
</span><span><span class="hljs-keyword">if</span></span><span> (!</span><span><span class="hljs-variable">$ok</span></span><span>) {
</span><span><span class="hljs-title function_ invoke__">fwrite</span></span><span>(STDERR, </span><span><span class="hljs-string">"posix_setpriority 失败(权限/平台/目标进程状态)\n"</span></span><span>);
}
} </span><span><span class="hljs-keyword">else</span></span><span> {
</span><span><span class="hljs-comment">// 方案 B:使用外部命令 renice(Linux/Unix)</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$pid</span></span><span>) {
</span><span><span class="hljs-comment">// 注意:在某些环境可能需要 sudo 或特权</span></span><span>
@</span><span><span class="hljs-title function_ invoke__">shell_exec</span></span><span>(</span><span><span class="hljs-title function_ invoke__">sprintf</span></span><span>(</span><span><span class="hljs-string">'renice +10 -p %d 2>/dev/null'</span></span><span>, </span><span><span class="hljs-variable">$pid</span></span><span>));
}
}
</span><span><span class="hljs-comment">// 清理</span></span><span>
</span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$pipes</span></span><span>[</span><span><span class="hljs-number">0</span></span><span>]); </span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$pipes</span></span><span>[</span><span><span class="hljs-number">1</span></span><span>]); </span><span><span class="hljs-title function_ invoke__">fclose</span></span><span>(</span><span><span class="hljs-variable">$pipes</span></span><span>[</span><span><span class="hljs-number">2</span></span><span>]);
</span><span><span class="hljs-variable">$exitCode</span></span><span> = </span><span><span class="hljs-title function_ invoke__">proc_close</span></span><span>(</span><span><span class="hljs-variable">$process</span></span><span>);
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"child exit code = <span class="hljs-subst">{$exitCode}</span></span></span><span>\n";
</span></span>
要点
proc_open() 返回的是句柄,并不会自动调整优先级。你需要获取 PID 后手动处理。
posix_setpriority() 比调用 renice 更可控、跨发行版差异更小,但要求启用 posix 扩展。
若你在容器或非 root 环境,只能提高 niceness(降优先级)。
如果你不需要在 PHP 里微调,也可以在命令行级别直接低优先级启动子进程:
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">declare</span></span><span>(strict_types=</span><span><span class="hljs-number">1</span></span><span>);
</span><span><span class="hljs-comment">// CPU 友好:nice +10</span></span><span>
</span><span><span class="hljs-variable">$cmd</span></span><span> = </span><span><span class="hljs-string">'nice -n 10 php worker.php'</span></span><span>;
</span><span><span class="hljs-comment">// I/O 友好:ionice class 2(best-effort),level 7(最低)</span></span><span>
</span><span><span class="hljs-variable">$cmd</span></span><span> = </span><span><span class="hljs-string">'ionice -c 2 -n 7 nice -n 10 php worker.php'</span></span><span>;
</span><span><span class="hljs-variable">$proc</span></span><span> = </span><span><span class="hljs-title function_ invoke__">proc_open</span></span><span>(</span><span><span class="hljs-variable">$cmd</span></span><span>, [
</span><span><span class="hljs-number">0</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'r'</span></span><span>],
</span><span><span class="hljs-number">1</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'w'</span></span><span>],
</span><span><span class="hljs-number">2</span></span><span> => [</span><span><span class="hljs-string">'pipe'</span></span><span>, </span><span><span class="hljs-string">'w'</span></span><span>],
], </span><span><span class="hljs-variable">$pipes</span></span><span>);
</span><span><span class="hljs-comment">// ... 省略与上文相同的清理逻辑</span></span><span>
</span></span>
提示
ionice 只影响 I/O 调度(磁盘),常与 nice 组合,CPU + I/O 双限流更稳。
某些容器最小镜像可能没有 ionice,要在镜像中安装(如 util-linux 包)。
检测返回值与告警
proc_nice() 返回 true/false,失败请记录日志并附带 posix_geteuid()、目标值等上下文。
posix_setpriority() 同理,失败常见于权限不足、PID 已退出。
记录实际优先级
Linux 可读取 /proc/<pid>/stat 的 nice 字段,或调用 ps -o pid,ni,cmd -p <pid> 验证。
在 PHP 中可以 exec('ps -o pid,ni,cmd -p ' . (int)$pid, $out) 并记录结果。
分层限流
仅靠 nice 对CPU 竞争有效;若还有并发量暴涨,请叠加队列/令牌桶(如 Redis + Lua)与进程数上限。
Linux/Unix:本文方法适用。非特权用户仅能增大 niceness(变“更好说话”)。
容器/系统服务:若服务由 systemd 管理,可在单元文件里设 Nice=,或授予 AmbientCapabilities=CAP_SYS_NICE。容器可在运行参数里加 --cap-add SYS_NICE。
macOS:语义类似 Linux,renice 和 POSIX 接口可用。
Windows:proc_nice() 不可用/不生效(常见为返回失败并触发警告)。Windows 的优先级应通过 proc_open() + wmic/PowerShell 或 FFI 调 WinAPI,属于不同实现路径,与本文不在同一套法则。
子进程内部:pcntl_fork() → 子进程 proc_nice(10~15)。
外部命令:proc_open() → proc_get_status() 取 PID → posix_setpriority($nice, $pid, POSIX_PRIO_PROCESS)。
一把梭:命令前加 nice -n <N>,I/O 密集再叠 ionice。
权限:负值需要特权;容器里考虑加 SYS_NICE 能力或退而求其次用正值。
验证:用 ps 或读取 /proc 校验;记录日志便于回溯。
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-keyword">declare</span></span><span>(strict_types=</span><span><span class="hljs-number">1</span></span><span>);
<span class="hljs-comment">/**
* 将指定 PID 的进程 niceness 调整为目标值(默认 10)。
* 返回 true 表示“看起来成功”(不保证最终生效,需结合 ps 验证)。
*/</span>
</span><span><span class="hljs-function"><span class="hljs-keyword">function</span></span></span><span> </span><span><span class="hljs-title">lower_priority</span></span><span>(</span><span><span class="hljs-params"><span class="hljs-keyword">int</span></span></span><span> </span><span><span class="hljs-variable">$pid</span></span><span>, </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$nice</span></span><span> = </span><span><span class="hljs-number">10</span></span><span>): </span><span><span class="hljs-title">bool</span></span><span>
{
</span><span><span class="hljs-comment">// 边界护栏</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$nice</span></span><span> < -</span><span><span class="hljs-number">20</span></span><span> || </span><span><span class="hljs-variable">$nice</span></span><span> > </span><span><span class="hljs-number">19</span></span><span>) {
</span><span><span class="hljs-keyword">throw</span></span><span> </span><span><span class="hljs-keyword">new</span></span><span> </span><span><span class="hljs-built_in">InvalidArgumentException</span></span><span>(</span><span><span class="hljs-string">'nice 必须在 -20..19 之间'</span></span><span>);
}
</span><span><span class="hljs-comment">// 首选 POSIX API</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">function_exists</span></span><span>(</span><span><span class="hljs-string">'posix_setpriority'</span></span><span>)) {
</span><span><span class="hljs-variable">$ok</span></span><span> = @</span><span><span class="hljs-title function_ invoke__">posix_setpriority</span></span><span>(</span><span><span class="hljs-variable">$nice</span></span><span>, </span><span><span class="hljs-variable">$pid</span></span><span>, POSIX_PRIO_PROCESS);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$ok</span></span><span>) {
</span><span><span class="hljs-keyword">return</span></span><span> </span><span><span class="hljs-literal">true</span></span><span>;
}
}
</span><span><span class="hljs-comment">// 备选:renice(仅 *nix)</span></span><span>
</span><span><span class="hljs-variable">$ret</span></span><span> = </span><span><span class="hljs-number">0</span></span><span>;
@</span><span><span class="hljs-title function_ invoke__">exec</span></span><span>(</span><span><span class="hljs-title function_ invoke__">sprintf</span></span><span>(</span><span><span class="hljs-string">'renice %s%d -p %d 2>/dev/null'</span></span><span>,
</span><span><span class="hljs-variable">$nice</span></span><span> >= </span><span><span class="hljs-number">0</span></span><span> ? </span><span><span class="hljs-string">'+'</span></span><span> : </span><span><span class="hljs-string">''</span></span><span>, </span><span><span class="hljs-variable">$nice</span></span><span>, </span><span><span class="hljs-variable">$pid</span></span><span>
), </span><span><span class="hljs-variable">$out</span></span><span>, </span><span><span class="hljs-variable">$ret</span></span><span>);
</span><span><span class="hljs-keyword">return</span></span><span> </span><span><span class="hljs-variable">$ret</span></span><span> === </span><span><span class="hljs-number">0</span></span><span>;
}
</span></span>