美文网首页
PHP进程间通信--信号量与共享内存

PHP进程间通信--信号量与共享内存

作者: 大胡子商人 | 来源:发表于2017-11-07 09:53 被阅读61次

PHP提供了两种实现共享内存的扩展。下面我们来一一讲解。

一、shmop 系类函数

<?php
/**
 * author: NickBai
 * createTime: 2016/12/5 0005 上午 9:17
 */
$shm_key = ftok(__FILE__, 't');

/**
开辟一块共享内存

int $key , string $flags , int $mode , int $size
$flags: a:访问只读内存段
c:创建一个新内存段,或者如果该内存段已存在,尝试打开它进行读写
w:可读写的内存段
n:创建一个新内存段,如果该内存段已存在,则会失败
$mode: 八进制格式  0655
$size: 开辟的数据大小 字节

 */

$shm_id = shmop_open($shm_key, "c", 0655, 1024);

/**
 * 写入数据 数据必须是字符串格式 , 最后一个指偏移量
 * 注意:偏移量必须在指定的范围之内,否则写入不了
 *
 */
$size = shmop_write($shm_id, 'hello world', 0);
echo "write into {$size}";

#读取的范围也必须在申请的内存范围之内,否则失败
$data = shmop_read($shm_id, 0, 100);
var_dump($data);

#删除 只是做一个删除标志位,同时不在允许新的进程进程读取,当在没有任何进程读取时系统会自动删除
shmop_delete($shm_id);

#关闭该内存段
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如何创建、操作共享内存,下面我们来看一下,他们如何在进程间通信发挥作用吧。

<?php
/**
 * author: NickBai
 * createTime: 2016/12/5 0005 上午 10:26
 */

//共享内存通信

//1、创建共享内存区域
$shm_key = ftok(__FILE__, 't');
$shm_id = shm_attach( $shm_key, 1024, 0655 );
const SHARE_KEY = 1;
$childList = [];

//2、开3个进程 读写 该内存区域
for( $i = 0; $i < 3; $i++ ){

    $pid = pcntl_fork();
    if( $pid == -1 ){
        exit('fork fail!' . PHP_EOL);
    }else if( $pid == 0 ){

        //子进程从共享内存块中读取 写入值 +1 写回
        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\n";

        exit( "child process " . getmypid() . " end!\n" );
    }else{
        $childList[$pid] = 1;
    }
}

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

//父进程读取共享内存中的值
$count = shm_get_var($shm_id, SHARE_KEY);
echo "final count is " . $count . PHP_EOL;


//3、去除内存共享区域
#从系统中移除
shm_remove($shm_id);
#关闭和共享内存的连接
shm_detach($shm_id);

逻辑很简单,开启3个进程,对同一个共享内存中的数据进行读写。有一个count的值,如果读到就+1,下面我们看一下运行结果:

image

从结果中我们可以看到,最终的 count 的值还是0。这是为什么呢?简单分析一下,不难发现,当我们开启创建进程的时候,3个子进程同时打开了 共享内存区域,此时他们几乎是同步的,所以读到的信息都是没有count值,此时他们执行自己的业务逻辑然后将 count 为0的结果写入内存区域。这并不是我们想要的结果,三个子进程互相抢占了资源,这是不合理的,那怎么规避这个问题呢?答案是通过 信号量 !

信号量

信号量是什么? 信号量 : 又称为信号灯、旗语 用来解决进程(线程同步的问题),类似于一把锁,访问前获取锁(获取不到则等待),访问后释放锁。

举一个生活中的例子:以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用

下面我们来看一下信号量的几个函数:

<?php
$key=ftok(__FILE__,'t');

/**
 * 获取一个信号量资源
 int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] 
 $max_acquire:最多可以多少个进程同时获取信号
 $perm:权限 默认 0666
 $auto_release:是否自动释放信号量
 */
$sem_id=sem_get($key);

#获取信号
sem_acquire($seg_id);

//do something 这里是一个原子性操作

//释放信号量
sem_release($seg_id);

//把次信号从系统中移除
sem_remove($sem_id);


//可能出现的问题
$fp = sem_get(fileinode(__DIR__), 100);
sem_acquire($fp);

$fp2 = sem_get(fileinode(__DIR__), 1));
sem_acquire($fp2);

注释的很详细了,不懂的还可以查看一下手册的介绍。那么我们现在就用信号量来修改我们的方法吧。

<?php
/**
 * author: NickBai
 * createTime: 2016/12/5 0005 上午 10:26
 */

//共享内存通信

//1、创建共享内存区域
$shm_key = ftok(__FILE__, 't');
$shm_id = shm_attach( $shm_key, 1024, 0655 );
const SHARE_KEY = 1;
$childList = [];

//加入信号量
$sem_id = ftok(__FILE__,'s');
$signal = sem_get( $sem_id );

//2、开3个进程 读写 该内存区域
for( $i = 0; $i < 3; $i++ ){

    $pid = pcntl_fork();
    if( $pid == -1 ){
        exit('fork fail!' . PHP_EOL);
    }else if( $pid == 0 ){

        // 获得信号量
        sem_acquire($signal);

        //子进程从共享内存块中读取 写入值 +1 写回
        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\n";
        // 用完释放
        sem_release($signal);
        exit( "child process " . getmypid() . " end!\n" );
    }else{
        $childList[$pid] = 1;
    }
}

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

//父进程读取共享内存中的值
$count = shm_get_var($shm_id, SHARE_KEY);
echo "final count is " . $count . PHP_EOL;


//3、去除内存共享区域
#从系统中移除
shm_remove($shm_id);
#关闭和共享内存的连接
shm_detach($shm_id);

运行结果:


image.png

完美的处理了进程之间抢资源的问题,实现了操作的原子性!

参考文章:
http://www.cnblogs.com/siqi/p/3999222.html
http://www.cnblogs.com/siqi/p/3997444.html
http://www.jianshu.com/p/08bcf724196b

相关文章

  • Linux

    操作系统 Linux进程间通信方式:Socket、共享内存、消息队列、信号量 epoll、select、poll ...

  • Linux知识

    Linux进程间通信方式有:消息队列,命名管道,信号量,共享内存,Berkeley套接字 等 临界区...

  • linux内核编程-IPC进程间通信

    进程间通信方式 方式管道(使用简单),信号量,信号(开销比较小),共享映射区(共享内存),消息队列,套接字(sok...

  • 共享内存

    Linux进程间通信 - 共享内存

  • Linux内核编程--字符设备文件,进行进程间通信,弄清open

    前言:进程间通信有: socket , 共享内存, 消息队列,信号量,信号,环境变量等 一、字符设备驱动框架流程:...

  • PHP进程间通信--信号量与共享内存

    PHP提供了两种实现共享内存的扩展。下面我们来一一讲解。 一、shmop 系类函数 注意两点:1、shmop_re...

  • Android

    ContentProvider 作用 进程间数据共享 即跨进程通信 原理 Binder进程间通信结合匿名共享内存(...

  • PHP进程通信-信号量和共享内存

    信号量与共享内存。共享内存是最快是进程间通信方式,因为n个进程之间并不需要数据复制,而是直接操控同一份数据。实际上...

  • Binder的介绍与原理分析

    介绍 1,首先进程间的通信方式:管道、消息队列、共享内存、信号量、信号、socket套接字、信号等2, binde...

  • System V IPC机制简介

    Unix System V 中的三种进程间通信机制: 消息队列 信号量(信号灯) 共享内存 这几个System V...

网友评论

      本文标题:PHP进程间通信--信号量与共享内存

      本文链接:https://www.haomeiwen.com/subject/ztudmxtx.html