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();
本人博客所有文章,均为原创。部分文章中或引用相关资料,但均已著明来源出处。可随意转载、分享,但需加本文链接,以及版权说明。