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

为什么需要进程间通信

当进程访问临界资源时,由于进程之间谁先执行并不确定(这取决于内核的进程调度算法,其中比较复杂)有可能多进程在相同的时间内同时访问临界资源,从而造成不可预料的错误。

临界资源
临界资源指的是一些虽作为共享资源却又无法同时被多个进程共同访问的共享资源。当有进程在使用临界资源时其他进程必须依据操作系统的同步机制等待占用进程释放该共享资源才可重新竞争使用共享资源。

共享内存
共享内存是系统在内存中开辟的一块公共的内存区域,任何一个进程都可以访问,在同一时刻,可以有多个进程访问该区域,为了保证数据的一致性,需要对该内存区域加锁或信号量。

信号量
信号量这个名字起的令人莫名其妙,但是看其英文原意,就十分容易理解。
semaphore 英[ˈseməfɔ:(r)] vt. 发出信号,打旗语;
类似于指挥官的作用。
举个栗子,使我们容易理解这个信号量在生活中的用法。理解之后可以套用到我们编程领域。
一家公司只有一个卫生间。那么当有人上厕所的时候,都要获取一把旗子(信号量)把旗子插在卫生间门口那盆菊花里,表示卫生间正在使用。那么员工上完厕所之后,就需要将旗子拔出来,释放(信号量),表示现在可以允许别人使用。通过一个简单的插拔旗子动作,我们就能够知道当前的厕所(共享内存)是否可以使用。

经典问题-哲学家吃饭
有5个哲学家,坐在一张桌子上吃饭,每个哲学家面前有两双筷子,当哲学家拥有左手边和右手边的两双筷子时才能吃饭。在这种情况下有一个极端的场景,5个哲学家同时拿起了左手边的筷子,当继续拿右手边筷子的时候发现已经被旁边的哲学家拿在了左手上。这就导致一种尴尬的场面,5个哲学家左手都有筷子,右手都空着,陷入等待右手边筷子的死循环,最后5个哲学家饿死了。。。。。。
解决办法:很简单,只要5个哲学家之间互相沟通就可以了,打旗语(semaphore)表示我要拿筷子吃饭了,你们先把我需要的筷子放着不要动,等我的旗子放下表示我吃完了,你们等我吃完筷子放下再去拿筷子。
《php进程间通信-信号量与共享内存》

演示代码

未使用信号量,多进程操作共享内存

<?php
$shm_key = ftok(__FILE__,'a');//创建共享内存唯一标识符
$shm_id = shm_attach($shm_key,1024,0755);//创建或打开共享内存段

$childList = [];
const SHARE_KEY = 1;
for ($i = 0;$i < 10;$i++){
    $pid = pcntl_fork();
    if ($pid == -1){
        // 创建失败
        exit("fork fail!".PHP_EOL);
    }elseif ($pid == 0){
        // 子进程执行程序
        sleep(rand(1,10));
        if (shm_has_var($shm_id,SHARE_KEY)){
            $value = shm_get_var($shm_id,SHARE_KEY);
            $value++;
            //模拟业务员处理时间
            sleep(rand(1,10));
            shm_put_var($shm_id,SHARE_KEY,$value);
        }else{
            $value = 0;
            shm_put_var($shm_id,SHARE_KEY,$value);
        }
        $pid = posix_getpid();
        echo "进程:{$pid},put value is {$value}\n";
        exit("child process {$pid} exit!\n");
    }else{
        // 父进程执行程序
        $childList[$pid] = 1;
    }

}
//回收子进程,子进程使用的所有系统资源将被释放
while (!empty($childList)){
    $pid = pcntl_wait($status);
    if ($pid > 0 ){
        unset($childList[$pid]);
    }
}
echo "结束啦\n";
shm_remove($shm_id);//从Unix系统中删除共享内存
shm_detach($shm_id);//从共享内存段断开连接

在fork10个进程后,每个进程会sleep(rand(1,10)),然后开始从共享内存里取出值,累加1,再保存到共享内存中。在不使用信号量的情况下,实际结果是杂乱的,进程之间抢占共享内存然后操作。
《php进程间通信-信号量与共享内存》

使用信号量,多进程操作共享内存

<?php
$shm_key = ftok(__FILE__,'a');
$shm_id = shm_attach($shm_key,1024,0755);

//加入信号量
$sem_id = ftok(__FILE__, 'b');
$signal = sem_get($sem_id);
$childList = [];
const SHARE_KEY = 1;
for ($i = 0;$i < 10;$i++){
    $pid = pcntl_fork();
    if ($pid == -1){
        exit("fork fail!".PHP_EOL);
    }elseif ($pid == 0){
        sem_acquire($signal);
        sleep(rand(1,10));
        if (shm_has_var($shm_id,SHARE_KEY)){
            $value = shm_get_var($shm_id,SHARE_KEY);
            $value++;
            //模拟业务员处理时间
            sleep(rand(1,10));
            shm_put_var($shm_id,SHARE_KEY,$value);
        }else{
            $value = 0;
            shm_put_var($shm_id,SHARE_KEY,$value);
        }
        $pid = posix_getpid();
        echo "进程:{$pid},put value is {$value}\n";
        sem_release($signal);
        exit("child process {$pid} exit!\n");
    }else{
        //父进程
        $childList[$pid] = 1;
    }

}
//回收子进程,子进程使用的所有系统资源将被释放
while (!empty($childList)){
    $pid = pcntl_wait($status);
    if ($pid > 0 ){
        unset($childList[$pid]);
    }
}
echo "结束啦\n";
sem_remove($signal);
shm_remove($shm_id);
shm_detach($shm_id);

当某一个进程拥有信号量之后,其余进程会等待该进程释放信号量,然后其余进程再去抢占信号量。多进程操作共享内存就变的有序了。代码运行结果如下
《php进程间通信-信号量与共享内存》

点赞