当前位置: 首页> 最新文章列表> 想控制子进程优先级?看看 PHP 中怎么用 proc_nice 实现

想控制子进程优先级?看看 PHP 中怎么用 proc_nice 实现

gitbox 2025-08-26

在高并发或批处理场景里,我们常常不希望某些后台任务“吃满”CPU,影响对外服务的响应。操作系统已经提供了调整当前进程的优先级;若要控制子进程,可以在fork 的子进程里调用它,或结合 proc_open()posix_setpriority() 针对特定 PID 调整。本文给出实战范式、坑点与排障建议。

小抄:在类 Unix 系统上,nice 取值范围通常是 -20(最高优先级)到 19(最低),默认是 0。

  • 普通用户只能增大 niceness(降低优先级),例如从 0 → 10。

  • 将优先级提高到负值(如 -5)通常需要 root 能力或 CAP_SYS_NICE


场景一:pcntl_fork() 后在子进程里“降权跑”

当你用 pcntl_fork() 派生子进程时,在子进程调用 proc_nice() 最直接。示例:父进程负责接活,子进程做重活但降优先级,避免拖慢整体。

<span><span><span class="hljs-meta">&lt;?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> &lt; </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>

要点

  1. proc_nice() 影响的是当前进程,因此要在子进程代码路径里调用。

  2. 调整为正值(如 10)通常不需要特权;设置负值可能失败。

  3. 如果只是“让子任务别抢资源”,把它设为 10~15 往往已经足够。


场景二:proc_open() 启动外部命令并调整它的优先级

若你不是用 fork,而是通过 proc_open() 启动其他命令(如 ffmpeg、convert、php 自己跑 worker),你需要拿到 PID 再调优先级。proc_get_status() 能取到子进程 PID;随后用 posix_setpriority()(POSIX 扩展)对该 PID 操作更直观。

<span><span><span class="hljs-meta">&lt;?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> =&gt; [</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> =&gt; [</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> =&gt; [</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>) &amp;&amp; </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&gt;/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(降优先级)


场景三:直接用 nice/ionice 启动(最快捷)

如果你不需要在 PHP 里微调,也可以在命令行级别直接低优先级启动子进程:

<span><span><span class="hljs-meta">&lt;?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> =&gt; [</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> =&gt; [</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> =&gt; [</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 包)。


错误处理与可观测性

  1. 检测返回值与告警

    • proc_nice() 返回 true/false,失败请记录日志并附带 posix_geteuid()、目标值等上下文。

    • posix_setpriority() 同理,失败常见于权限不足PID 已退出

  2. 记录实际优先级

    • Linux 可读取 /proc/<pid>/statnice 字段,或调用 ps -o pid,ni,cmd -p <pid> 验证。

    • 在 PHP 中可以 exec('ps -o pid,ni,cmd -p ' . (int)$pid, $out) 并记录结果。

  3. 分层限流

    • 仅靠 nice 对CPU 竞争有效;若还有并发量暴涨,请叠加队列/令牌桶(如 Redis + Lua)与进程数上限


平台与权限细节

  • Linux/Unix:本文方法适用。非特权用户仅能增大 niceness(变“更好说话”)。

  • 容器/系统服务:若服务由 systemd 管理,可在单元文件里设 Nice=,或授予 AmbientCapabilities=CAP_SYS_NICE。容器可在运行参数里加 --cap-add SYS_NICE

  • macOS:语义类似 Linux,renice 和 POSIX 接口可用。

  • Windowsproc_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">&lt;?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> &lt; -</span><span><span class="hljs-number">20</span></span><span> || </span><span><span class="hljs-variable">$nice</span></span><span> &gt; </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&gt;/dev/null'</span></span><span>,
        </span><span><span class="hljs-variable">$nice</span></span><span> &gt;= </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>