PHP程式間通訊

thread發表於2019-02-16

PHP是用C編寫的,因此它對系統底層API的操作與C很像,同大多數語言一樣,PHP程式間通訊的方式有以下幾種:訊息佇列,管道,共享記憶體,socket和訊號。本文是對這幾種通訊方式對整理:

管道通訊PIPE

管道用於承載簡稱之間的通訊資料。為了方便理解,可以將管道比作檔案,程式A將資料寫到管道P中,然後程式B從管道P中讀取資料。php提供的管道操作API與操作檔案的API基本一樣,除了建立管道使用posix_mkfifo函式,讀寫等操作均與檔案操作函式相同。當然,你可以直接使用檔案模擬管道,但是那樣無法使用管道的特性了。

通過管道通訊的大概思路是,首先建立一個管道,然後子程式向管道中寫入資訊,父程式從管道中讀取資訊,這樣就可以做到父子程式直接實現通訊了。

<?php

// 建立管道
$pipePath = "pipe";
if( !file_exists( $pipePath ) ){
    if( !posix_mkfifo( $pipePath, 0666) ){
        exit(`make pipe false!` . PHP_EOL);
    }
}

// 建立程式,子程式寫管道,父程式讀管道
// 通過 pcntl_fork函式建立一個子程式。
// pcntl_fork 函式 很特殊,它呼叫一次擁有 多個返回值。
// 在父程式中:它返回 子程式的ID 這個值是 大於0 的。
// 在子程式中,它返回0。當返回 -1 時表示建立程式失敗。
$pid = pcntl_fork();

if( $pid == 0 ){
    // 子程式寫管道
    $file = fopen( $pipePath, `w`);
    fwrite( $file, `hello world`);
    sleep(1);
    exit;
}else{
    // 父程式讀管道
    $file = fopen( $pipePath, `r`);
    // 設定成讀取非阻塞
    // 當讀取是非阻塞的情況下,父程式進行讀取資訊的時候不會等待,
    // 管道中沒有訊息也會立馬返回。
    // stream_set_blocking( $file, False); 
    echo fread( $file, 20) . PHP_EOL;

    pcntl_wait($status); // 回收子程式
}

訊息佇列

訊息佇列是存放在記憶體中的一種佇列資料結構。

<?php

// 獲取父程式id
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}
";
$childList = array();
// 建立訊息佇列,定義訊息型別
$id = ftok(__FILE__, `m`);
$msgQueue = msg_get_queue($id);
const MSG_TYEP = 1;

// 生產者
function producer()
{
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ($i = 0; $i <= $repeatNum; $i++) {
        $str = "({$pid}) progress create! {$i}";
        msg_send($msgQueue, MSG_TYEP, $str);
        $rand = rand(1, 3);
        sleep($rand);
    }
}

// 消費者
function consumer()
{
    global $msgQueue;
    $pid = posix_getpid();
    $repeatNum = 6;
    for ($i = 1; $i<= $repeatNum; $i++) {
        $rel = msg_receive($msgQueue, MSG_TYEP, $msgType, 1024, $message);
        echo "{$message} | consumer({$pid}) destroy 
";
        $rand = rand(1, 3);
        sleep($rand);
    }
}

function createProgress($callback)
{
    $pid = pcntl_fork();
    if ($pid == -1) {
        // 建立失敗
        exit("fork progresses error
");
    } elseif ($pid == 0) {
        // 子程式執行程式
        $pid = posix_getpid();
        $callback();
        exit("({$pid})child progress end!
");
    } else {
        // 父程式
        return $pid;
    }
}

for ($i = 0; $i < 3; $i++) {
    $pid = createProgress(`producer`);
    $childList[$pid] = 1;
    echo "create producer progresses: {$pid}
";
}

for ($i = 0; $i < 2; $i++) {
    $pid = createProgress(`consumer`);
    $childList[$pid] = 1;
    echo "create consumer progresses: {$pid}
";
}

while (!empty($childList)) {
    $childPid = pcntl_wait($status);
    if ($childPid > 0) {
        unset($childList[$childPid]);
    }
}
echo "({$parentPid})main progress end!
";

執行結果:

create producer progresses: 21432
create producer progresses: 21433
create producer progresses: 21434
create consumer progresses: 21435
(21426) progress create! 2 | consumer(21435) destroy
(21424) progress create! 1 | consumer(21436) destroy
create consumer progresses: 21436
(21426) progress create! 3 | consumer(21436) destroy
(21426) progress create! 4 | consumer(21435) destroy
(21425) progress create! 3 | consumer(21436) destroy
(21424) progress create! 2 | consumer(21435) destroy
(21426) progress create! 5 | consumer(21435) destroy
(21424) progress create! 3 | consumer(21436) destroy
(21433)child progress end!
(21425) progress create! 4 | consumer(21435) destroy
(21424) progress create! 4 | consumer(21436) destroy
(21434)child progress end!
(21424) progress create! 5 | consumer(21435) destroy
(21425) progress create! 5 | consumer(21436) destroy
(21432)child progress end!
(21435)child progress end!
(21436)child progress end!
(21431)main progress end!

訊號量與共享記憶體

<?php
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}
";

// 建立共享記憶體,建立訊號量,定義共享key
// ftok(檔案路徑,資源識別符號) 建立一個IPC通訊所需的id
$shm_id = ftok(__FILE__, `m`);
$shm_id = ftok(__FILE__, `s`);
// shm_attach(id) 建立或者開啟一個共享記憶體
$shareMemory = shm_attach($shm_id);
// 返回一個可使用者訪問系統訊號量的id
$signal = sem_get($shm_id);
const SHARE_KEY = 1;
// 生產者
function producer() {
    global $shareMemory;
    global $signal;
    $pid = posix_getpid();
    $repeatNum = 5;
    for ($i = 1; $i <= $repeatNum; $i++) {
        // 獲得訊號量 - 阻塞程式,直到訊號量被獲取到[lock鎖機制的關鍵]
        sem_acquire($signal);

        // 檢查某個key是否存在與共享記憶體中
        if (shm_has_var($shareMemory, SHARE_KEY)) {
            // 獲取共享記憶體中的key的值
            $count = shm_get_var($shareMemory, SHARE_KEY);
            $count ++;
            // 為共享記憶體中的key賦值
            shm_put_var($shareMemory, SHARE_KEY, $count);
            echo "({$pid}) count: {$count}
";
        } else {
            // 初始化
            shm_put_var($shareMemory, SHARE_KEY, 0);
            echo "({$pid}) count: 0
";
        }
        // 釋放
        sem_release($signal);
    }
}

function createProgress($callback) {
    $pid = pcntl_fork();
    if ($pid == -1) {
        // 建立失敗
        exit("fork progress error!
");
    } elseif ($pid == 0) {
        // 子程式
        $pid = posix_getpid();
        $callback();
        exit("({$pid}) child progress end!
");
    } else {
        // 父程式
        return $pid;
    }
}

// 3個寫程式
for ($i = 0; $i < 3; $i ++) {
    $pid = createProgress(`producer`);
    $childList[$pid] = 1;
    echo "create producer child progress: {$pid} 
";
}

// 等待所有子程式
while (!empty($childList)) {
    $childPid = pcntl_wait($status);
    if ($childPid > 0) {
        unset($childList[$childPid]);
    }
}

// 釋放共享記憶體與訊號量
shm_remove($shareMemory);
sem_remove($signal);
echo "({$parentPid}) main progress end!
";

執行結果:
使用訊號量來實現共享記憶體的鎖機制

parent progress pid:31720
create producer child progress: 31721 
create producer child progress: 31722 
(31721) count: 0
(31721) count: 1
(31721) count: 2
(31721) count: 3
(31721) count: 4
(31721) child progress end!
create producer child progress: 31723 
(31722) count: 5
(31722) count: 6
(31722) count: 7
(31722) count: 8
(31722) count: 9
(31722) child progress end!
(31723) count: 10
(31723) count: 11
(31723) count: 12
(31723) count: 13
(31723) count: 14
(31723) child progress end!
(31720) main progress end!

無鎖情況

Warning: sem_release(): SysV semaphore 4357894312 (key 0x73048925) is not currently acquired in /Users/easyboom/www/example/訊號量與共享記憶體.php on line 38

相關文章