在單獨的一個PHP程式中讀寫、建立、刪除共享記憶體方面上你應該沒有問題了。但是實際執行中不可能只是一個PHP程式在執行中。如果在多個程式的情況下你還是沿用單個程式的處理方法,你一定會碰到問題--著名的並行和互斥問題。比如說有2個程式同時需要對同一段記憶體進行讀寫。當兩個程式同時執行寫入操作時,你將得到一個錯誤的資料,因為該段記憶體將之可能是最後執行的程式的內容,甚至是由2個程式寫入的資料輪流隨機出現的一段混合的四不象。這顯然是不能接受的。為了解決這個問題,我們必須引入互斥機制。互斥機制在很多作業系統的教材上都有專門講述,這裡不多重複。實現互斥機制的最簡單辦法就是使用訊號燈。訊號量是另外一種程式間(IPC)的方式,它同其他IPC機構(管道、FIFO、訊息佇列)不同。
說到訊號量可能大家都很陌生,作為php肯定知道mysql、redis中的鎖,當然還有php檔案鎖。說白了就是鎖,用來解決程式(執行緒同步的問題),訪問前獲取鎖(獲取不到則等待),訪問後釋放鎖。
訊號量的作用就是,考慮是否有多個程式同時寫入資料到共享記憶體的情況,是否需要避免衝突。
舉一個生活中的例子:以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然後放下車攔,剩下的車則必須在入口等待,此後來的車也都不得不在入口處等待。這時,有一輛車離開停車場,看門人得知後,開啟車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。在這個停車場系統中,車位是公共資源,每輛車好比一個執行緒,看門人起的就是訊號量的作用。
記得給環境開啟兩個擴充套件【enable-shmop --enable-sysvsem】
因為php預設不支援這些函式,所以需要重編譯php。如要使用:
System V訊號量,編譯時加上 –enable-sysvsem
System V共享記憶體,編譯時加上 –enable-sysvshm
System V訊息佇列,編譯時加上 –enable-sysvmsg
Shared Memory,編譯時加上 –enable-shmop
訊號量系列函式
<?php
//1、建立訊號量唯一識別符號
$key = 0x4337b101;
//2、建立訊號量資源ID
$sem_resouce_id = sem_get($key);
//3、接受訊號量
sem_acqure($sem_resource_id);
//4、釋放訊號量
sem_release($sem_resource_id);
//5、銷燬訊號量
sem_remove($sem_resource_id);
簡單小案例
<?php
$key = 0x4337b101;
$sem_id = sem_get($key);
//請求訊號控制權
if (sem_acquire($sem_id)) {
$shm_id = shmop_open($key, 'c', 0644, 1024);
//讀取並寫入資料
$count = (int) shmop_read($shm_id, 0, 8) + 1;
shmop_write($shm_id, str_pad($count, 8, '0', STR_PAD_LEFT), 0);
// echo shmop_read($shm_id, 0, 8);
//關閉記憶體塊
shmop_close($shm_id);
//釋放訊號
sem_release($sem_id);
}
如果出現報錯:Warning: sem_release(): SysV semaphore 140680297324568 (key 0x4337b101) is not currently acquired in /usr/local/nginx/html/index.php on line 38
那是因為沒有獲得鎖~
在Linux下命令觀察,檢視系統共享記憶體,訊號量,佇列
# ipcs
# ipcs -s //單獨檢視訊號量的話,使用ipcs -s命令
稍微複雜的案例
<?php
//建立共享記憶體區域
$shm_key = ftok(__FILE__, 'a');
$shm_id = shm_attach($shm_key, 1024, 0755);
//var_dump($shm_id);die(); resource(4) of type (sysvshm)
const SHARE_KEY = 1;
$child_list = [];
//加入訊號量
$sem_id = ftok(__FILE__, 'b');
$signal = sem_get($sem_id);
//$signal resource(5) of type (sysvsem)
for ($i = 0; $i < 3; $i++) {
$pid = pcntl_fork();
if ($pid == -1) {
exit("Fork fail!".PHP_EOL);
} elseif ($pid == 0) {
//獲取訊號量
sem_acquire($signal);
if (shm_has_var($shm_id,SHARE_KEY)) {
$count = shm_get_var($shm_id, SHARE_KEY);
$count++;
//模擬業務處理
$sec = rand(1, 3);
sleep($sec);
shm_put_var($shm_id, SHARE_KEY, $count);
} else {
$count = 0;
$sec = rand(1, 3);
sleep($sec);
shm_put_var($shm_id, SHARE_KEY, $count);
}
echo "child process: ".getmypid()." is writing! now count is: $count ".PHP_EOL;
//釋放訊號量
sem_release($signal);
exit("child process".getmypid()."end".PHP_EOL);
} else {
$child_list[] = $pid;
}
}
while (count($child_list) > 0) {
foreach ($child_list as $key => $pid) {
$status = pcntl_waitpid($pid, $status);
if ($status > 0 || $status == -1) {
unset($child_list[$key]);
}
}
sleep(1);
}
$count = shm_get_var($shm_id, SHARE_KEY);
echo " $count ".PHP_EOL;
//銷燬訊號量
sem_remove($signal);
shm_remove($shm_id);
shm_detach($shm_id);
實際運用中根據場景靈活運用就可以了~