php作為腳本程序,通常生命周期都很短,如在web應用中,一次請求就是php運行的一個周期,請求結束則生命周期截止。所以php在處理需要共 享的資源時,一般會將共享數據保存在數據庫或dbm之類的文件中,再者就是利用內存實現共享。你可以選擇已有的工具輔助你,像memcache;也可以自 己編寫代碼訪問操作系統的共享內存段。
php中對共享內存段的操作有兩組函數:System V IPC和Shared Memory。 其中System V IPC系列函數能夠更方便的操作數據,無需像Shared Memory那樣必須自己掌握讀寫時的偏移量、長度等,也不用序列化/反序列化來回轉換(因為Shared Memory函數只支持字符串格式的數據參數)。但是System V IPC系列不支持Windows,所以如果要在win環境下使用,只能選Shared Memory。
因為php默認不支持這些函數,所以需要重編譯php。如要使用:
System V信號量,編譯時加上 –enable-sysvsem
System V共享內存,編譯時加上 –enable-sysvshm
System V消息隊列,編譯時加上 –enable-sysvmsg
Shared Memory,編譯時加上 –enable-shmop
先寫個Shared Memory的例子:
<?php
$key = ftok(__FILE__, 'i');
$size = 100;
$shm_h = @shmop_open($key, 'c', 0644, $size);
if($shm_h === false) {
echo "shmop open failed";
exit;
}
$data = shmop_read($shm_h, 0, $size);
$data = unserialize($data);
//如果沒有數據則寫一個
if(empty($data)) {
echo "there is no data";
$data = "imdonkey";
//就算數據是文本,write時也要序列化
$write_size = shmop_write($shm_h, serialize($data), 0);
if($write_size === false) echo "shmop write failed!";
}
//如果有,顯示出來,之后刪掉
else {
echo "shared memory data: ";
print_r($data);
shmop_delete($shm_h);
}
shmop_close($shm_h);
?>
再寫個System V shm的例子:
<?php
$shm_key = ftok(__FILE__, 'i');
$memsize = 120;
$shm_h = shm_attach($shm_key, $memsize, 0644);
if($shm_h === false) {
echo "shmop open failed";
exit;
}
$var_key = 3;
$data = @shm_get_var($shm_h, $var_key);
if(empty($data)) {
$data = "imdonkey";
echo "there is no data, insert $data.\n";
shm_put_var($shm_h, $var_key, $data);
} else {
echo "find data: $data\n";
shm_remove_var($shm_h, $var_key);
}
shm_detach($shm_h);
?>
可以看到,sysV對於每個數據都另外設立了對應的var_key,這樣在同一內存區域可以保存多個數據,而不用像shmop中那樣再申請另外一個共享內存區域,還免除了序列化的干擾(雖然數據最終還是以序列化的形式保存,但不用開發者去手動實現)。
例子雖然簡單,但也有一些需要注意的地方,不管是shm_attach還是shmop_open,所申請的內存的大小一定要滿足后面數據的體積,這 個體積包括數據本身序列化后的長,還有php添加的少量header信息。php官方文檔中有人提出了一種計算要申請的內存大小的公式,這個公式可以保證 所申請的內存足夠存儲一個指定的數據。公式如下:
//當shm_attach第一次被調用時,php向共享內存寫入一個header
$shmHeaderSize = (PHP_INT_SIZE * 4) + 8;
//當shm_put_var調用時,php會在序列化后的數據前面,加一個header
$shmVarSize = (((strlen(serialize($foo))+ (4 * PHP_INT_SIZE)) /4 ) * 4 ) + 4;
$memsize = $shmHeaderSize + $shmVarSize;
這個公式是否適用於所有情況,我不敢說,所以我想最好還是在程序中,將准備放入共享內存的數據結構設計好,盡量保證數據大小在某一范圍內。
還有就是為了防止共享內存被浪費,當數據無用時及時調用對應的remove方法釋放資源。
介紹完共享內存再順帶提一下消息隊列Message Queue(也是在System V IPC函數組中),消息隊列似乎可以視為另一種共享內存,只是數據存儲的方式有些不同。簡單來說,就是每個key對應一個隊列,每個隊列可以保存多個數據,數據間按照先進先出的原則進行操作。php文檔中的例子很好的介紹了各函數的應用:
<?php
if ( sizeof($argv)<2 ) {
echo "Usage: $argv[0] stat|send|receive|remove msgType MSG [msg] \n\n" ;
echo " EX: $argv[0] send 1 \"This is no 1\" \n" ;
echo " $argv[0] receive ID \n" ;
echo " $argv[0] stat \n" ;
echo " $argv[0] remove \n" ;
exit;
}
$MSGKey = "123456" ;
$seg = msg_get_queue($MSGKey) ;
switch ( $argv[1] ) {
case "send":
msg_send($seg, $argv[2], $argv[3]);
echo "msg_send done...\n" ;
break;
case "receive":
$stat = msg_stat_queue( $seg );
echo 'Messages in the queue: '.$stat['msg_qnum']."\n";
if ( $stat['msg_qnum']>0 ) {
msg_receive($seg, $argv[2], $msgtype, 1024, $data, true, MSG_IPC_NOWAIT);
var_dump($msgtype);
var_dump($data);
echo "\n";
}
else {
echo "No Msg...\n";
}
break;
case "stat":
print_r( msg_stat_queue($seg) );
break;
case "remove":
msg_remove_queue($seg);
break;
}
?>
消息隊列中的數據同樣受到大小的約束,具體約束范圍可通過msg_stat_queue的msg_qbytes看到。這段代碼唯一有點小改動的地方就在接受消息時,指定了MSG_IPC_NOWAIT,不然如果目標隊列沒有數據,默認會一直等待。
一般會用到共享內存或消息隊列的情況,都會涉及到多線程/進程,或跨語言的數據傳遞。如果是php腳本/進程間共享數據,那只要小心點操作就沒什么 問題。如果要求跨語言,那很可能遇到千奇百怪的問題,呵呵,我還沒試過,但在網上看到別人發的苦水貼,以后有機會一定實驗一下。
在調試共享內存、信號量、消息隊列時,可以配合Linux系統命令觀察數據存儲情況及信號量、消息隊列資源分配情況,如ipcs, ipcrm命令。
利用PHP操作Linux消息隊列完成進程間通信
當我們開發的系統需要使用多進程方式運行時,進程間通信便成了至關重要的環節。消息隊列(message queue)是Linux系統進程間通信的一種方式。
關於Linux系統進程通信的概念及實現可查看:http://www.ibm.com/developerworks/cn/linux/l-ipc/
關於Linux系統消息隊列的概念及實現可查看:http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/
PHP的sysvmsg模塊是對Linux系統支持的System V IPC中的System V消息隊列函數族的封裝。我們需要利用sysvmsg模塊提供的函數來進進程間通信。先來看一段示例代碼_1:
<?php
$message_queue_key
=
ftok
(
__FILE__
,
'a'
);
$message_queue
= msg_get_queue(
$message_queue_key
, 0666);
var_dump(
$message_queue
);
$message_queue_status
= msg_stat_queue(
$message_queue
);
print_r(
$message_queue_status
);
//向消息隊列中寫
msg_send(
$message_queue
, 1,
"Hello,World!"
);
$message_queue_status
= msg_stat_queue(
$message_queue
);
print_r(
$message_queue_status
);
//從消息隊列中讀
msg_receive(
$message_queue
, 0,
$message_type
, 1024,
$message
, true, MSG_IPC_NOWAIT);
print_r(
$message
.
"\r\n"
);
msg_remove_queue(
$message_queue
);
?>
這段代碼的運行結果如下:
resource(4) of type (sysvmsg queue)
Array
(
[msg_perm.uid] => 1000
[msg_perm.gid] => 1000
[msg_perm.mode] => 438
[msg_stime] => 0
[msg_rtime] => 0
[msg_ctime] => 1279849495
[msg_qnum] => 0
[msg_qbytes] => 16384
[msg_lspid] => 0
[msg_lrpid] => 0
)
Array
(
[msg_perm.uid] => 1000
[msg_perm.gid] => 1000
[msg_perm.mode] => 438
[msg_stime] => 1279849495
[msg_rtime] => 0
[msg_ctime] => 1279849495
[msg_qnum] => 1
[msg_qbytes] => 16384
[msg_lspid] => 2184
[msg_lrpid] => 0
)
Hello,World!
可以看到已成功從消息隊列中讀取“Hello,World!”字符串
下面列舉一下示例代碼中的主要函數:
ftok
( string
$pathname
, string
$proj
)
手冊上給出的解釋是:Convert a pathname
and
a project identifier to a System V IPC key。這個函數返回的鍵值唯一對應linux系統中一個消息隊列。在獲得消息隊列的引用之前都需要調用這個函數。
msg_get_queue ( int
$key
[, int
$perms
] )
msg_get_queue() 會根據傳入的鍵值返回一個消息隊列的引用。如果linux系統中沒有消息隊列與鍵值對應,msg_get_queue()將會創建一個新的消息隊列。函數 的第二個參數需要傳入一個int值,作為新創建的消息隊列的權限值,默認為0666。這個權限值與linux命令
chmod
中使用的數值是同一個意思,因為在linux系統中一切皆是文件。
msg_send ( resource
$queue
, int
$msgtype
, mixed
$message
[, bool
$serialize
[, bool
$blocking
[, int &
$errorcode
]]] )
顧名思義,該函數用來向消息隊列中寫數據。
msg_stat_queue ( resource
$queue
)
這個函數會返回消息隊列的元數據。消息隊列元數據中的信息很完整,包括了消息隊列中待讀取的消息數、最后讀寫隊列的進程ID等。示例代碼在第8行調用該函數返回的數組中隊列中待讀取的消息數msg_qnum值為0。
msg_receive ( resource
$queue
, int
$desiredmsgtype
, int &
$msgtype
, int
$maxsize
, mixed &
$message
[, bool
$unserialize
[, int
$flags
[, int &
$errorcode
]]] )
msg_receive用於讀取消息隊列中的數據。
msg_remove_queue ( resource
$queue
)
msg_remove_queue用於銷毀一個隊列。
示例代碼_1只是展示了PHP操作消息隊列函數的應用。下面的代碼具體描述了進程間通信的場景
<?php
$message_queue_key
=
ftok
(
__FILE__
,
'a'
);
$message_queue
= msg_get_queue(
$message_queue_key
, 0666);
$pids
=
array
();
for
(
$i
= 0;
$i
< 5;
$i
++) {
//創建子進程
$pids
[
$i
] = pcntl_fork();
if
(
$pids
[
$i
]) {
echo
"No.$i child process was created, the pid is $pids[$i]\r\n"
;
}
elseif
(
$pids
[
$i
] == 0) {
$pid
= posix_getpid();
echo
"process.$pid is writing now\r\n"
;
msg_send(
$message_queue
, 1,
"this is process.$pid's data\r\n"
);
posix_kill(
$pid
, SIGTERM);
}
}
do
{
msg_receive(
$message_queue
, 0,
$message_type
, 1024,
$message
, true, MSG_IPC_NOWAIT);
echo
$message
;
//需要判斷隊列是否為空,如果為空就退出
//break;
}
while
(true)
?>
運行結果為:
No.0 child process was created, the pid is 5249
No.1 child process was created, the pid is 5250
No.2 child process was created, the pid is 5251
No.3 child process was created, the pid is 5252
No.4 child process was created, the pid is 5253
process.5251 is writing now
this is process.5251's data
process.5253 is writing now
process.5252 is writing now
process.5250 is writing now
this is process.5253's data
this is process.5252's data
this is process.5250's data
process.5249 is writing now
this is process.5249's data
這段程序每次的運行結果都會不同,這正說明了多進程的異步性。從結果也能看出消息隊列FIFO特性。
以上便是我研究的一點心得。接下來將會繼續研究PHP利用信號、socket等進行進程間通信的方法。