爲什麼需要進程間通信
當進程訪問臨界資源時,由於進程之間誰先執行並不確定(這取決於內核的進程調度算法,其中比較複雜)有可能多進程在相同的時間內同時訪問臨界資源,從而造成不可預料的錯誤。
臨界資源
臨界資源指的是一些雖作爲共享資源卻又無法同時被多個進程共同訪問的共享資源。當有進程在使用臨界資源時其他進程必須依據操作系統的同步機制等待佔用進程釋放該共享資源纔可重新競爭使用共享資源。
共享內存
共享內存是系統在內存中開闢的一塊公共的內存區域,任何一個進程都可以訪問,在同一時刻,可以有多個進程訪問該區域,爲了保證數據的一致性,需要對該內存區域加鎖或信號量。
信號量
信號量這個名字起的令人莫名其妙,但是看其英文原意,就十分容易理解。
semaphore 英[ˈseməfɔ:(r)] vt. 發出信號,打旗語;
類似於指揮官的作用。
舉個栗子,使我們容易理解這個信號量在生活中的用法。理解之後可以套用到我們編程領域。
一家公司只有一個衛生間。那麼當有人上廁所的時候,都要獲取一把旗子(信號量)把旗子插在衛生間門口那盆菊花裏,表示衛生間正在使用。那麼員工上完廁所之後,就需要將旗子拔出來,釋放(信號量),表示現在可以允許別人使用。通過一個簡單的插拔旗子動作,我們就能夠知道當前的廁所(共享內存)是否可以使用。
經典問題-哲學家吃飯
有5個哲學家,坐在一張桌子上吃飯,每個哲學家面前有兩雙筷子,當哲學家擁有左手邊和右手邊的兩雙筷子時才能吃飯。在這種情況下有一個極端的場景,5個哲學家同時拿起了左手邊的筷子,當繼續拿右手邊筷子的時候發現已經被旁邊的哲學家拿在了左手上。這就導致一種尷尬的場面,5個哲學家左手都有筷子,右手都空着,陷入等待右手邊筷子的死循環,最後5個哲學家餓死了。。。。。。
解決辦法:很簡單,只要5個哲學家之間互相溝通就可以了,打旗語(semaphore)表示我要拿筷子吃飯了,你們先把我需要的筷子放着不要動,等我的旗子放下表示我吃完了,你們等我吃完筷子放下再去拿筷子。
演示代碼
未使用信號量,多進程操作共享內存
<?php
$shm_key = ftok(__FILE__,'a');//創建共享內存唯一標識符
$shm_id = shm_attach($shm_key,1024,0755);//創建或打開共享內存段
$childList = [];
const SHARE_KEY = 1;
for ($i = 0;$i < 10;$i++){
$pid = pcntl_fork();
if ($pid == -1){
// 創建失敗
exit("fork fail!".PHP_EOL);
}elseif ($pid == 0){
// 子進程執行程序
sleep(rand(1,10));
if (shm_has_var($shm_id,SHARE_KEY)){
$value = shm_get_var($shm_id,SHARE_KEY);
$value++;
//模擬業務員處理時間
sleep(rand(1,10));
shm_put_var($shm_id,SHARE_KEY,$value);
}else{
$value = 0;
shm_put_var($shm_id,SHARE_KEY,$value);
}
$pid = posix_getpid();
echo "進程:{$pid},put value is {$value}\n";
exit("child process {$pid} exit!\n");
}else{
// 父進程執行程序
$childList[$pid] = 1;
}
}
//回收子進程,子進程使用的所有系統資源將被釋放
while (!empty($childList)){
$pid = pcntl_wait($status);
if ($pid > 0 ){
unset($childList[$pid]);
}
}
echo "結束啦\n";
shm_remove($shm_id);//從Unix系統中刪除共享內存
shm_detach($shm_id);//從共享內存段斷開連接
在fork10個進程後,每個進程會sleep(rand(1,10))
,然後開始從共享內存裏取出值,累加1,再保存到共享內存中。在不使用信號量的情況下,實際結果是雜亂的,進程之間搶佔共享內存然後操作。
使用信號量,多進程操作共享內存
<?php
$shm_key = ftok(__FILE__,'a');
$shm_id = shm_attach($shm_key,1024,0755);
//加入信號量
$sem_id = ftok(__FILE__, 'b');
$signal = sem_get($sem_id);
$childList = [];
const SHARE_KEY = 1;
for ($i = 0;$i < 10;$i++){
$pid = pcntl_fork();
if ($pid == -1){
exit("fork fail!".PHP_EOL);
}elseif ($pid == 0){
sem_acquire($signal);
sleep(rand(1,10));
if (shm_has_var($shm_id,SHARE_KEY)){
$value = shm_get_var($shm_id,SHARE_KEY);
$value++;
//模擬業務員處理時間
sleep(rand(1,10));
shm_put_var($shm_id,SHARE_KEY,$value);
}else{
$value = 0;
shm_put_var($shm_id,SHARE_KEY,$value);
}
$pid = posix_getpid();
echo "進程:{$pid},put value is {$value}\n";
sem_release($signal);
exit("child process {$pid} exit!\n");
}else{
//父進程
$childList[$pid] = 1;
}
}
//回收子進程,子進程使用的所有系統資源將被釋放
while (!empty($childList)){
$pid = pcntl_wait($status);
if ($pid > 0 ){
unset($childList[$pid]);
}
}
echo "結束啦\n";
sem_remove($signal);
shm_remove($shm_id);
shm_detach($shm_id);
當某一個進程擁有信號量之後,其餘進程會等待該進程釋放信號量,然後其餘進程再去搶佔信號量。多進程操作共享內存就變的有序了。代碼運行結果如下