當前位置: 首頁> 最新文章列表> 想控制子進程優先級?看看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>