PHP多进程环境下通过共享内存与信号量实现资源共享

PHP多进程环境下通过共享内存与信号量实现资源共享

目前工作环境,由于一些原因,不能使用swoole,和其他多进程的管理组件。但是项目中有大量的功能必须通过多进程来实现。面对这也不能,那也不能的困境,总要想一些办法来多快好省的完成工作。

项目中其他成员,使用多进程的方式,通过shell 起多个脚本,完成多进程的开发,效果也不错。我这边主要使用pcntl_fork 这个PHP自带的扩展来起进程。

在项目的开发过程中,比如我需要通过10个进程消费100万的数据,在进程环境内,无法将多个进程已经消费的数据汇总起来。

需求点:

多进程环境下,进程之间通信困难,编写通信代码也比较繁琐。
进程间通信,比如获取值,对值进行操作,面临锁的问题。

下面是通过共享内存,与信号量实现的进程间通信

<?php

class Test
{
    private $processArr = [];
    private $shmId = null;
    private $semId = null;
    private $ftokId = null;

    const SHARE_MEMORY_SIZE = 4096;  //申请4k 作为共享内存

    //共享内存配置类似
    private $shareKeyConfig = array(
        "count"   => array(
            "start" => 0,
            "end"   => 127,
        ),
        "consume" => array(
            "start" => 128,
            "end"   => 255,
        ),
    );

    /**
     * 初始化参数
     * Test constructor.
     * @param array $shareKeyConfig
     */
    public function __construct($shareKeyConfig = [])
    {
        if ($shareKeyConfig) {
            $this->shareKeyConfig = $shareKeyConfig;
        }
        return true;
    }


    public function renderProgress($current)
    {
        printf("progress: [%-50s] %d%% Done\r", str_repeat('#', $current / 600 * 50), $current / 600 * 100);
    }



    /**
     * 启动一个进程
     */
    public function run()
    {
        $this->initShareMemoryLock();
        for ($i = 0; $i <= 5; $i++) {
            $pid = pcntl_fork();
            if ($pid == -1) {
                die("创建子进程失败");
            } elseif ($pid > 0) {
                //echo "子进程{$pid}已经正常启动" . PHP_EOL;
                $this->processArr[$pid] = $pid;
            } else {
                $this->dosomeThing();
                exit();
            }
        }
        //echo "done" . PHP_EOL;
        $this->waitProcess();
        //echo "done2" . PHP_EOL;
        $this->removeShareMemoryLock();
    }


    public function dosomeThing()
    {
        for ($i = 1; $i <= 100; $i++) {
            usleep(100000);
            $this->incr("consume");
            $num = (int)$this->getValue('consume');
            $this->renderProgress($num);
            //echo "当前内存中的数字是:{$num}" . PHP_EOL;
        }
        //sleep(1);
        //echo "加完毕" . PHP_EOL;
        return true;
    }


    public function waitProcess()
    {
        while (count($this->processArr)) {
            $childPid = pcntl_wait($status);
            //var_dump($childPid);
            if ($childPid > 0) {
                //echo "子进程{$childPid}已经正常结束" . PHP_EOL;
                unset($this->processArr[$childPid]);
            }
        }
        return true;
    }

    /**
     * 初始化基于共享内存的锁
     */
    public function initShareMemoryLock()
    {
        $this->checkFunctionExists();
        $this->createShareMemoryCache(1);
        $this->createSemaphore(1);
        return true;
    }

    /**
     * 计数器
     * @param $key
     * @param int $incr
     */
    public function incr($key, $incr = 1)
    {
        sem_acquire($this->semId);
        $num = (int)$this->getValue($key);
        $this->setValue($key, $num + $incr);
        sem_release($this->semId);
        return true;
    }

    /**
     * 根据key,获取对应的value
     * @param string $key
     */
    private function getValue(string $key)
    {
        if (!isset($this->shareKeyConfig[$key])) {
            die('请先配置共享内存的key!' . PHP_EOL);
        }
        $config = $this->shareKeyConfig[$key];
        $val    = shmop_read($this->shmId, $config['start'], ($config['end'] - $config['start']));
        return trim($val);
    }

    /**
     * 设置共享内存
     * @param string $key
     * @param string $val
     */
    private function setValue(string $key, string $val)
    {
        if (!isset($this->shareKeyConfig[$key])) {
            die('请先配置共享内存的key!' . PHP_EOL);
        }
        $config = $this->shareKeyConfig[$key];
        shmop_write($this->shmId, $val, $config['start']);
        return true;
    }

    /**
     * 创建共享内存
     * 单个文件多个方法调用,必须使用不同的projectId
     * @param $projectId
     * @param int $size
     * @return bool
     */
    public function createShareMemoryCache($projectId, $size = 2048)
    {
        set_time_limit(0);
        if ($projectId < 1 || $projectId > 255) {
            die("projectId 的取值范围在1-255。" . PHP_EOL);
        }

        $this->ftokId = ftok(__FILE__, $projectId);
        $shmId        = @shmop_open($this->ftokId, "c", 0644, $size);
        if (!is_resource($shmId)) {
            die("shmop_open(): unable to attach or create shared memory segment 'Permission denied'" . PHP_EOL);
        }
        $this->shmId = $shmId;
        return true;
    }

    /**
     * 关闭共享内存快
     */
    public function closeShareMemory()
    {
        shmop_delete($this->shmId);
        shmop_close($this->shmId);
        return true;
    }

    /**
     * 关闭信号量
     */
    public function closeSemaphore()
    {
        sem_remove($this->semId);
        return true;
    }


    /**
     * 关闭共享内存
     * @return bool
     */
    public function removeShareMemoryLock()
    {
        $this->closeSemaphore();
        $this->closeShareMemory();
        return true;
    }


    /**
     *  创建信号量
     * @param $projectId
     */
    public function createSemaphore($projectId)
    {
        $this->semId = sem_get($this->ftokId);
        return true;
    }

    /**
     * 检测系统是否开启共享内存与信号量函数
     * @return bool
     */
    public function checkFunctionExists()
    {
        $requireFunc = array(
            "ftok",
            "shmop_open",
            "shmop_write",
            "shmop_read",
            "shmop_delete",
            "shmop_close",
            "shmop_size",
            "sem_get",
            "sem_acquire",
            "sem_release",
            "sem_remove"
        );

        foreach ($requireFunc as $func) {
            if (!function_exists($func)) {
                die("$func 删除不存在");
            }
        }
        return true;
    }
}

$demo = new Test();
$demo->run();

posted @ 2020-04-10 16:51  roverliang  阅读(773)  评论(0编辑  收藏  举报