php進程間通信--信號量與共享內存


  首先我們來講解一下,php如何實現共享內存。(注意:本示例是在linux下,請勿在windows下嘗試此代碼,並且必須是在php-cli模式下)

  php提供了兩種實現共享內存的擴展。下面我們來一一講解。

  一、shmop 系類函數

 1 <?php
 2 /**
 3  * author: NickBai
 4  * createTime: 2016/12/5 0005 上午 9:17
 5  */
 6 $shm_key = ftok(__FILE__, 't');
 7 
 8 /**
 9 開辟一塊共享內存
10 
11 int $key , string $flags , int $mode , int $size
12 $flags: a:訪問只讀內存段
13 c:創建一個新內存段,或者如果該內存段已存在,嘗試打開它進行讀寫
14 w:可讀寫的內存段
15 n:創建一個新內存段,如果該內存段已存在,則會失敗
16 $mode: 八進制格式  0655
17 $size: 開辟的數據大小 字節
18 
19  */
20 
21 $shm_id = shmop_open($shm_key, "c", 0655, 1024);
22 
23 /**
24  * 寫入數據 數據必須是字符串格式 , 最后一個指偏移量
25  * 注意:偏移量必須在指定的范圍之內,否則寫入不了
26  *
27  */
28 $size = shmop_write($shm_id, 'hello world', 0);
29 echo "write into {$size}";
30 
31 #讀取的范圍也必須在申請的內存范圍之內,否則失敗
32 $data = shmop_read($shm_id, 0, 100);
33 var_dump($data);
34 
35 #刪除 只是做一個刪除標志位,同時不在允許新的進程進程讀取,當在沒有任何進程讀取時系統會自動刪除
36 shmop_delete($shm_id);
37 
38 #關閉該內存段
39 shmop_close($shm_id);

  注意兩點:

  1、shmop_read 函數 第2個參數 是讀取的起始位置,第3個參數是要讀取的長度,如果你要讀取的長度小於信息長度,原信息會被截斷成你指定的長度。

  2、shmop_write 函數 僅可寫 字符串 內容!

 

  二、用 Semaphore 擴展中的 sem 類函數 (用起來更方便,類似 key-value 格式)

<?php
/**
 * author: NickBai
 * createTime: 2016/12/5 0005 上午 9:28
 */
// Get the file token key
$key = ftok(__FILE__, 'a');
$shar_key = 1;

// 創建一個共享內存
$shm_id = shm_attach($key, 1024, 0666); // resource type
if ($shm_id === false) {
    die('Unable to create the shared memory segment' . PHP_EOL);
}

#設置一個值
shm_put_var($shm_id, $shar_key, 'test');

#刪除一個key
//shm_remove_var($shm_id, $shar_key);

#獲取一個值
$value = shm_get_var($shm_id,  $shar_key);
var_dump($value);

#檢測一個key是否存在
// var_dump(shm_has_var($shm_id,  $shar_key));

#從系統中移除
shm_remove($shm_id);

#關閉和共享內存的連接
shm_detach($shm_id);

  shm_put_var 第三個參數 寫入的值 是一個混合類型,所以沒有shmop_write的局限性。

  注意:$shar_key 只能是 int 型的參數。

 

  介紹完了php如何創建、操作共享內存,下面我們來看一下,他們如何在進程間通信發揮作用吧。

 1 <?php
 2 /**
 3  * author: NickBai
 4  * createTime: 2016/12/5 0005 上午 10:26
 5  */
 6 
 7 //共享內存通信
 8 
 9 //1、創建共享內存區域
10 $shm_key = ftok(__FILE__, 't');
11 $shm_id = shm_attach( $shm_key, 1024, 0655 );
12 const SHARE_KEY = 1;
13 $childList = [];
14 
15 //2、開3個進程 讀寫 該內存區域
16 for( $i = 0; $i < 3; $i++ ){
17 
18     $pid = pcntl_fork();
19     if( $pid == -1 ){
20         exit('fork fail!' . PHP_EOL);
21     }else if( $pid == 0 ){
22 
23         //子進程從共享內存塊中讀取 寫入值 +1 寫回
24         if ( shm_has_var($shm_id, SHARE_KEY) ){
25             // 有值,加一
26             $count = shm_get_var($shm_id, SHARE_KEY);
27             $count ++;
28             //模擬業務處理邏輯延遲
29             $sec = rand( 1, 3 );
30             sleep($sec);
31 
32             shm_put_var($shm_id, SHARE_KEY, $count);
33         }else{
34             // 無值,初始化
35             $count = 0;
36             //模擬業務處理邏輯延遲
37             $sec = rand( 1, 3 );
38             sleep($sec);
39 
40             shm_put_var($shm_id, SHARE_KEY, $count);
41         }
42 
43         echo "child process " . getmypid() . " is writing ! now count is $count\n";
44 
45         exit( "child process " . getmypid() . " end!\n" );
46     }else{
47         $childList[$pid] = 1;
48     }
49 }
50 
51 // 等待所有子進程結束
52 while( !empty( $childList ) ){
53     $childPid = pcntl_wait( $status );
54     if ( $childPid > 0 ){
55         unset( $childList[$childPid] );
56     }
57 }
58 
59 //父進程讀取共享內存中的值
60 $count = shm_get_var($shm_id, SHARE_KEY);
61 echo "final count is " . $count . PHP_EOL;
62 
63 
64 //3、去除內存共享區域
65 #從系統中移除
66 shm_remove($shm_id);
67 #關閉和共享內存的連接
68 shm_detach($shm_id);

  邏輯很簡單,開啟3個進程,對同一個共享內存中的數據進行讀寫。有一個count的值,如果讀到就+1,下面我們看一下運行結果:

  從結果中我們可以看到,最終的 count 的值還是0。這是為什么呢?簡單分析一下,不難發現,當我們開啟創建進程的時候,3個子進程同時打開了 共享內存區域,此時他們幾乎是同步的,所以讀到的信息都是沒有count值,此時他們執行自己的業務

邏輯然后將 count 為0的結果寫入內存區域。這並不是我們想要的結果,三個子進程互相搶占了資源,這是不合理的,那怎么規避這個問題呢?答案是通過 信號量 !

 

  信號量

  信號量是什么? 信號量 : 又稱為信號燈、旗語 用來解決進程(線程同步的問題),類似於一把鎖,訪問前獲取鎖(獲取不到則等待),訪問后釋放鎖。

  舉一個生活中的例子:以一個停車場的運作為例。簡單起見,假設停車場只有三個車位,一開始三個車位都是空的。這時如果同時來了五輛車,看門人允許其中三輛直接進入,然后放下車攔,剩下的車則必須在入口等待,此后來的車也都不得不在入口

處等待。這時,有一輛車離開停車場,看門人得知后,打開車攔,放入外面的一輛進去,如果又離開兩輛,則又可以放入兩輛,如此往復。在這個停車場系統中,車位是公共資源,每輛車好比一個線程,看門人起的就是信號量的作用

  下面我們來看一下信號量的幾個函數:

 1 <?php
 2 $key=ftok(__FILE__,'t');
 3 
 4 /**
 5  * 獲取一個信號量資源
 6  int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] 
 7  $max_acquire:最多可以多少個進程同時獲取信號
 8  $perm:權限 默認 0666
 9  $auto_release:是否自動釋放信號量
10  */
11 $sem_id=sem_get($key);
12 
13 #獲取信號
14 sem_acquire($seg_id);
15 
16 //do something 這里是一個原子性操作
17 
18 //釋放信號量
19 sem_release($seg_id);
20 
21 //把次信號從系統中移除
22 sem_remove($sem_id);
23 
24 
25 //可能出現的問題
26 $fp = sem_get(fileinode(__DIR__), 100);
27 sem_acquire($fp);
28 
29 $fp2 = sem_get(fileinode(__DIR__), 1));
30 sem_acquire($fp2);

  注釋的很詳細了,不懂的還可以查看一下手冊的介紹。那么我們現在就用信號量來修改我們的方法吧。

 1 <?php
 2 /**
 3  * author: NickBai
 4  * createTime: 2016/12/5 0005 上午 10:26
 5  */
 6 
 7 //共享內存通信
 8 
 9 //1、創建共享內存區域
10 $shm_key = ftok(__FILE__, 't');
11 $shm_id = shm_attach( $shm_key, 1024, 0655 );
12 const SHARE_KEY = 1;
13 $childList = [];
14 
15 //加入信號量
16 $sem_id = ftok(__FILE__,'s');
17 $signal = sem_get( $sem_id );
18 
19 //2、開3個進程 讀寫 該內存區域
20 for( $i = 0; $i < 3; $i++ ){
21 
22     $pid = pcntl_fork();
23     if( $pid == -1 ){
24         exit('fork fail!' . PHP_EOL);
25     }else if( $pid == 0 ){
26 
27         // 獲得信號量
28         sem_acquire($signal);
29 
30         //子進程從共享內存塊中讀取 寫入值 +1 寫回
31         if ( shm_has_var($shm_id, SHARE_KEY) ){
32             // 有值,加一
33             $count = shm_get_var($shm_id, SHARE_KEY);
34             $count ++;
35             //模擬業務處理邏輯延遲
36             $sec = rand( 1, 3 );
37             sleep($sec);
38 
39             shm_put_var($shm_id, SHARE_KEY, $count);
40         }else{
41             // 無值,初始化
42             $count = 0;
43             //模擬業務處理邏輯延遲
44             $sec = rand( 1, 3 );
45             sleep($sec);
46 
47             shm_put_var($shm_id, SHARE_KEY, $count);
48         }
49 
50         echo "child process " . getmypid() . " is writing ! now count is $count\n";
51         // 用完釋放
52         sem_release($signal);
53         exit( "child process " . getmypid() . " end!\n" );
54     }else{
55         $childList[$pid] = 1;
56     }
57 }
58 
59 // 等待所有子進程結束
60 while( !empty( $childList ) ){
61     $childPid = pcntl_wait( $status );
62     if ( $childPid > 0 ){
63         unset( $childList[$childPid] );
64     }
65 }
66 
67 //父進程讀取共享內存中的值
68 $count = shm_get_var($shm_id, SHARE_KEY);
69 echo "final count is " . $count . PHP_EOL;
70 
71 
72 //3、去除內存共享區域
73 #從系統中移除
74 shm_remove($shm_id);
75 #關閉和共享內存的連接
76 shm_detach($shm_id);

  運行結果:

  完美的處理了進程之間搶資源的問題,實現了操作的原子性!

 

  參考文章:

  http://www.cnblogs.com/siqi/p/3999222.html

  http://www.cnblogs.com/siqi/p/3997444.html

  http://www.jianshu.com/p/08bcf724196b


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM