首先 pcntl 是 php 的擴充套件,其中 pcntl_fork()
是建立程式函式,檢視 php 手冊,pcntl 是 程式控制擴充套件
。
大多數對這塊還是比較陌生的,和開發沒啥關係吧,的確,是沒啥太大關係的。
而 ps -ef | grep php
或者 pstree -apn | grep php
想必一般都用過,列印出來的是這樣的:
上圖中一個 master
程式下面並列兩個分支,就是子程式了。
使用過 workman 的也很熟悉上面的結構,workman 底層是 php 寫的,依賴於 php 擴充套件,其中 pcntl
就是其中之一很重要的依賴擴充套件,它的 master
worker
結構就是這個擴充套件實現的。
迴歸正題,使用 pcntl_fork()
實現一下,fork
翻譯為 “分叉”,fork 一個子程式 即建立一個子程式。
<?php
$pid = pcntl_fork();
//父程式和子程式都會執行下面程式碼
if ($pid == -1) {
//錯誤處理:建立子程式失敗時返回-1.
die('could not fork');
} else if ($pid) {
//父程式會得到子程式號,所以這裡是父程式執行的邏輯
pcntl_wait($status); //等待子程式中斷,防止子程式成為殭屍程式。
} else {
//子程式得到的$pid為0, 所以這裡是子程式執行的邏輯。
}
這段程式碼是官方示例程式碼,具體過程是執行到 pcntl_fork()
時 “拷貝” 一份原檔案(程式碼是一模一樣的哦)
也就是說 pcntl_fork();
的一霎那,之後的程式碼同時在兩個程式裡執行(包括給 $pid
賦值),所以 $pid
在兩個程式裡的賦值是不一樣的,在父程式裡返回的 $pid
大於 0,等於子程式的程式 id
,通常叫做 pid
(process id)。而子程式裡返回的 $pid
為 0。
所以通過 if
判斷,就區分開了哪個程式碼塊裡應該執行哪個角色的邏輯。master-worker
模型,一般master
負責管理子程式數量等、處理訊號啥的,不處理業務邏輯,而 worker
程式,顧名思義,工作程式。
<?php
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父程式執行程式碼塊
$master_pid = posix_getpid(); //獲得當前程式的 pid
$worker_pids[] = $pid; // 收集子程式 pid
echo "master_pid:".$master_pid.EOL;
var_dump($worker_pids);
pcntl_wait($status); //等待子程式中斷,防止子程式成為殭屍程式。
echo PHP_EOL.'等待了10秒後..status:'.$status;
} else {
// 子程式得到的$pid為0, 所以這裡是子程式執行的邏輯。
// 幹活
//為了不讓程式過早退出 我們 sleep 一下
sleep(10);
}
類似於 nginx 的程式結構出來了
pcntl_wait($status);
是防止子程式成為殭屍程式的函式,是阻塞的,類似監聽。也就是當子程式執行完退出時,或者主動 exit()
時,父程式將捕獲狀態碼賦予 $status
,並且清理子程式。
最後一個點,前面提到:執行到 pcntl_fork()
時 “拷貝” 一份原檔案。注意的一點是,子程式繼承了父程式的變數和方法,但由於是兩個程式(兩個指令碼), 在子程式中給變數重新賦值,不會影響父程式裡的變數值,程式之間是隔離的。
<?php
$i = 1;
$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// 父程式
echo '我是父程式:'.$i.PHP_EOL;
} else {
// 子程式賦值
$i = 2;
sleep(1);
echo '我是子程式:'.$i.PHP_EOL;
}
echo '兩個程式都會執行:'.$i.PHP_EOL;
執行下
可以看到先列印出來的 1
是父程式輸出的,後面的 2
是子程式輸出的。
本作品採用《CC 協議》,轉載必須註明作者和本文連結