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