鑑於上兩篇文章,有些同學對php的多程式表現出很大的興趣,所以我又要來獻醜了,感覺不錯就點贊,感覺有幫助就打賞,反正要鼓勵一下寶寶,哈哈哈。
程式是系統進行資源分配和排程的基本單位,執行一個php程式,預設只會生成一個程式,當該程式發生請求IO時,就會被掛起,讓出CPU,php的多程式程式設計的目的就是要在多個程式併發的執行任務,當其中一些程式由於發生IO被掛起時,還有一些程式可以利用CPU執行任務,這樣就可以充分利用系統資源來完成我們的大批次任務了(特別適合IO型任務)。php多程式開發,主要有兩條路:
- 第一就是php內建的pcntl擴充套件(依賴linux系統),
- 第二就是比較流行的擴充套件swoole下的多程式。
以下我們來實際操作一波!
1. pcntl_fork
不多說了,直接上程式碼:
$pid = pcntl_fork(); //fork一個子程式
if ($pid > 0) { //父程式會獲取到fork到的子程式的程式id
dump('我是父程式');
} elseif ($pid == 0) { //$pid=0時為剛剛派生出的子程式
dump('我是子程式');
} else {
dump('建立子程式失敗');
}
執行結果如下,證明建立成功:
就那麼簡單嗎?不是的。還有很多關鍵程式建立和使用過程要注意的點,以下再來深入探索一下:
- 孤兒程式
孤兒程式已經很白話了,建立它的父程式死掉了,但是它自己還沒死,還在執行,但是linux憲法規定,凡是父程式死了還在執行的子程式都要認程式號為1的程式做父親,這樣的話,子程式就是可以脫離ssh,直接程式後臺執行模式,由乾爹程式1來管理了。利用這一點,可以開發守護程式。
$pid = pcntl_fork();
if ($pid > 0) {
$seconds = 5;
while ($seconds--) {
dump('我是父程式,我的壽命只有5秒,剩餘:' . $seconds);
sleep(1);
}
dump('我是父程式');
} elseif ($pid == 0) {
$seconds = 10;
while ($seconds--) {
dump('我是子程式,我的壽命有10秒,剩餘::' . $seconds);
sleep(1);
}
} else {
dump('建立子程式失敗');
}
執行結果如下
前5秒,父子程式協同執行,保證正常的父子關係
systemd,1 --switched-root --system --deserialize 22
├─sshd,1090 -D -u0
│ ├─sshd,3711
│ │ └─sshd,3713
│ │ └─bash,3714
│ │ └─sudo,3741 -i
│ │ └─bash,3742
│ │ └─php,11178 think debug //父程式
│ │ └─php,11179 think debug //子程式
後5秒,父程式死掉了,子程式退出ssh,認了程式號為1的程式做乾爹,繼續執行5秒,執行完畢被程式1收回,如果一直執行就成為守護程式了。
systemd,1 --switched-root --system --deserialize 22
├─php,11179 think debug
- 殭屍程式
殭屍程式就是已經執行完了,exit了,但是父程式沒有主動回收資源,父子程式關係沒有變化,所以依然存在於程式表中。由於殭屍程式會佔用系統資源,所以是有危害的,應該避免。怎麼模擬產生呢,很簡單,將上面的程式碼變一下,使父程式長命於子程式,如下:
$pid = pcntl_fork();
if ($pid > 0) {
$seconds = 10;
while ($seconds--) {
dump('我是父程式,我的壽命只有10秒,剩餘:' . $seconds);
sleep(1);
}
dump('我是父程式');
} elseif ($pid == 0) {
$seconds = 5;
while ($seconds--) {
dump('我是子程式,我的壽命有5秒,剩餘::' . $seconds);
sleep(1);
}
} else {
dump('建立子程式失敗');
}
}
執行結果如下:
前5秒,父子程式協同執行,保證正常的父子關係
systemd,1 --switched-root --system --deserialize 22
├─sshd,1090 -D -u0
│ ├─sshd,3711
│ │ └─sshd,3713
│ │ └─bash,3714
│ │ └─sudo,3741 -i
│ │ └─bash,3742
│ │ └─php,11204 think debug
│ │ └─php,11205 think debug
後5秒,子程式死掉了,父進正常執行,但是子程式還存在於程式表中,佔用系統資源,直到父程式也死掉才會一起被釋放。
├─sshd,1090 -D -u0
│ ├─sshd,3711
│ │ └─sshd,3713
│ │ └─bash,3714
│ │ └─sudo,3741 -i
│ │ └─bash,3742
│ │ └─php,11204 think debug
│ │ └─(php,11205) //殭屍程式
- 避免殭屍程式和孤兒程式
想要避免出現殭屍程式和孤兒程式,主要是保證兩點:一是,父程式不能早死於子程式;二是,父程式能及時回收執行結束的子程式。這方面,主要使用php提供了兩個函式來解決:pcntl_wait、pcntl_waitpid。編輯程式碼如下:
$pid = pcntl_fork();
if ($pid) {
dump('這裡是父程式');
pcntl_wait($status); //主程式會掛起,等待第一個子程式結束
//等同於, pcntl_waitpid($pid,$status);
sleep(10); //模擬幹活
} elseif ($pid == 0) {
sleep(20); //模擬幹活
dump('這裡是子程式');
} else {
dump('建立子程式異常');
}
執行上面程式碼,發現殭屍程式和孤兒程式問題解決了,因為主程式始終保持等待和回收子程式,但是這樣之後,任務執行變成了序列的、同步的,根本實現不了父子程式同時幹活的目的。
- 多程式非同步非阻塞工作
毫不猶豫,懟程式碼:
$pids = [];
for ($i = 0; $i < 20; $i++) {
$pid = pcntl_fork();
if ($pid) {
$pids[] = $pid;
} elseif ($pid == 0) {
sleep(20);
dump('這裡是子程式');
exit();//執行完,一定要結束,不然就會走進建立子程式的死迴圈
} else {
dump('建立子程式異常');
}
}
//等待回收所有子程式
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
//第三個引數有兩個可能值:WNOHANG-回收子程式時非同步不掛起主程式
// WUNTRACED-子程式已經退出並且其狀態未報告時返回
}
執行結果如下,整個過程20秒結束
├─sshd,1073 -D -u0
│ ├─sshd,3133
│ │ └─sshd,3135
│ │ └─bash,3136
│ │ └─sudo,3163 -i
│ │ └─bash,3164
│ │ └─php,3361 think debug
│ │ ├─php,3363 think debug
│ │ ├─php,3364 think debug
│ │ ├─php,3365 think debug
│ │ ├─php,3366 think debug
│ │ ├─php,3367 think debug
│ │ ├─php,3368 think debug
│ │ ├─php,3369 think debug
│ │ ├─php,3370 think debug
│ │ ├─php,3371 think debug
│ │ ├─php,3372 think debug
│ │ ├─php,3373 think debug
│ │ ├─php,3374 think debug
│ │ ├─php,3375 think debug
│ │ ├─php,3376 think debug
│ │ ├─php,3377 think debug
│ │ ├─php,3378 think debug
│ │ ├─php,3379 think debug
│ │ ├─php,3380 think debug
│ │ ├─php,3381 think debug
│ │ └─php,3382 think debug
這裡的程式碼設計,主程式負責建立、回收子程式,子程式負責實際的幹活,整個過程都是非同步的,所以執行效率非常高。
- 程式間通訊-共享記憶體
php程式間的共享記憶體函式有兩大類,簡介如下:
I:首先簡單介紹一下shmop_xxx類
相關函式,使用起來也簡單,不做一一解釋了(偷懶)
編輯程式碼如下
$key = ftok(__FILE__, 'c'); //ipc標識
$shmid = shmop_open($key, 'c', 0655, 1024);//申請1024位元組的記憶體空間
$pid = pcntl_fork();
if ($pid > 0) {
sleep(2);//睡眠一下,等待子程式寫資料進記憶體
$info = shmop_read($shmid, 0, 100);//讀取記憶體資料
dump('我是父程式,從共享程式中讀取到的資訊是:' . $info);
pcntl_wait($status); //回收子程式
} elseif ($pid == 0) {
//從申請的共享記憶體的第0個位元組開始寫資料
$size = shmop_write($shmid, "hello father,i'm your son!", 0);
dump('我是子程式,我向記憶體裡寫入位元組數是:' . $size);
exit();
} else {
dump('建立子程式失敗');
}
執行結果如下,父子程式能夠透過透過共享記憶體實現資訊交流
II:再來介紹一下shm_xxx類,需要安裝Semaphore 擴充套件
相關函式,使用起來也簡單,不做一一解釋了(再次偷懶)
編輯程式碼如下:
$key = ftok(__FILE__, 'c'); //ipc標識
$shmid = shm_attach($key, 1024, 0655);
$variable_key = 1; //類似設定key-value的key,不過這裡要求是int
$pid = pcntl_fork();
if ($pid > 0) {
sleep(2);//睡眠一下,等待子程式寫資料進記憶體
$info = shm_get_var($shmid, $variable_key);//獲取對應key的值
dump('我是父程式,從共享程式中讀取到的資訊是:' . $info);
pcntl_wait($status); //回收子程式
} elseif ($pid == 0) {
//對key設定值
$size = shm_put_var($shmid, $variable_key, "hello father,i'm your son!");
dump('我是子程式,我向記憶體裡寫入位元組數是:' . $size);
exit();
} else {
dump('建立子程式失敗');
}
shm_remove($shmid); // 從系統中移除
shm_detach($shmid); //關閉和共享記憶體的連線
III:除了上面的方式之外,還可以使用Redis、memcache等方式實現程式間的資料共享
- 程式間通訊-訊號量
訊號量相關函式如下,如需細究,請自行研究:
以下我懟一下自己粗狂的程式碼:
$key = ftok(__FILE__, 'c'); //ipc標識
$signal_id = sem_get($key); //獲取訊號id
$pids = [];
for ($i = 0; $i < 5; $i++) {
$pid = pcntl_fork();
if ($pid > 0) {
$pids[] = $pid;
sem_acquire($signal_id); //獲取訊號量(阻塞式)
$datetime = date('Y-m-d H:i:s');
dump("我是父程式,正在建立程式:{$i},建立時間:{$datetime}");
sem_release($signal_id);//釋放訊號量
} elseif ($pid == 0) {
sem_acquire($signal_id); //獲取訊號量(阻塞式)
sleep(2);
sem_release($signal_id);//釋放訊號量
exit();
} else {
dump('建立子程式失敗');
}
sem_remove($signal_id);//刪除訊號id
//等待回收所有子程式
foreach ($pids as $pid) {
pcntl_waitpid($pid, $status);
}
}
執行結果如下:
可以發現,父程式不是一下子生成5個子程式的,因為子程式做了訊號量控制,當某個程式獲得訊號量時,其他程式處於等待訊號量時間,只有擁有訊號量的程式釋放訊號量時,其他程式才能獲得,所以上面父程式每次建立子程式後,要等子程式睡兩秒,然後再繼續建立下去,如此類推。
2. swoole多程式
swoole的官網學習文件,https://wiki.swoole.com/wiki/page/p-proces...
程式是十分昂貴的資源,php每個程式佔用記憶體從幾M到幾百M不等,過多的建立程式反而會嚴重拖垮系統,使系統變得緩慢,甚至當機。php程式都是獨立的執行單位,程式間的資料庫、Redis連線都是獨立的,要做好資源使用控制和程式間通訊。php多程式程式設計適用於cli模式,對於php-fpm執行模式下,儘量不要採取多程式程式設計,會出現無法預測的問題。
原創不易,分享快樂,渴望動力
本作品採用《CC 協議》,轉載必須註明作者和本文連結