共享内存:
为了在多个进程间交换信息, 内核专门留出了一块内存区由需要访问的进程将其映射到自己私有地址空间, 多个进程共享一段内存.
共享内存的优点:
共享内存是最快的IPC, 它的快速体现在, 进行数据共享而进行的数据复制非常少.
比如使用消息队列, 一个进程往消息队列写消息时, 需要将数据从用户空间拷贝到内核空间,
另一个进程读消息时, 需要将数据从内核空间拷贝到用户空间.
而共享内存无需像消息队列这样的两次数据复制, 进程是从各自的地址空间直接访问共享的内存段.
由于多个进程共享一段内存, 为了保证数据的同步, 通常使用信号量+共享内存来同步数据.
信号量:
信号量用来解决进程(线程)同步的问题, 类似于一把锁, 访问前获取锁(获取不到则等待), 访问后释放锁.
ftok ( string $pathname , string $proj ) : int
Convert a pathname and a project identifier to a System V IPC key
把一个路径和标识符转换成IPC的key
shm_attach ( int $key [, int $memsize [, int $perm = 0666 ]] ) : resource
创建或打开共享内存
shm_put_var ( resource $shm_identifier , int $variable_key , mixed $variable ) : bool
在共享内存中新增或更新变量
shm_get_var ( resource $shm_identifier , int $variable_key ) : mixed
从共享内存中获取一个变量
shm_remove ( resource $shm_identifier ) : bool
删除指定共享内存的所有数据
shm_detach ( resource $shm_identifier ) : bool
关闭和指定共享内存的连接
shm_has_var ( resource $shm_identifier , int $variable_key ) : bool
检查共享内存中是否存在指定变量
sem_get ( int $key [, int $max_acquire = 1 [, int $perm = 0666 [, int $auto_release = 1 ]]] ) : resource
创建信号量
sem_acquire ( resource $sem_identifier [, bool $nowait = FALSE ] ) : bool
获得信号量
sem_release ( resource $sem_identifier ) : boo
释放信号量
代码:
<?php
// 创建一个共享内存
$key = ftok(__FILE__, 'a');
$shm_id = shm_attach($key, 1024, 0666);
$pid = pcntl_fork();
switch ($pid) {
case -1:
die('fork failed');
break;
case 0:
// 子进程往共享内存中写入变量
shm_put_var($shm_id, 99, 'val1');
break;
default:
// 父进程阻塞, 在子进程结束之后, 从共享内存中获取变量
pcntl_wait($status);
echo shm_get_var($shm_id, 99) . PHP_EOL;
break;
}
// 删除指定共享内存的所有数据
shm_remove($shm_id);
// 关闭和指定共享内存的连接
shm_detach($shm_id);
运行结果:
[root@e2963c647c8b www]# php shm.php
val1
代码:
<?php
// 创建一个共享内存
$key = ftok(__FILE__, 'a');
$shm_id = shm_attach($key, 1024, 0666);
// 存储子进程的进程ID
$childs = [];
// variable_key
const VARIABLE_KEY = 99;
// 创建3个子进程
for ($i = 0; $i < 3; $i++) {
$pid = pcntl_fork();
switch ($pid) {
case -1:
die('fork failed');
break;
case 0:
// 共享内存中存在值, +1
if (shm_has_var($shm_id, VARIABLE_KEY)) {
$val = shm_get_var($shm_id, VARIABLE_KEY);
$val++;
// 睡眠
sleep(rand(1, 3));
// 在共享内存中更新一个变量
shm_put_var($shm_id, VARIABLE_KEY, $val);
}
// 共享内存中不存在值, 初始化为0
else {
// 睡眠
sleep(rand(1, 3));
// 在共享内存中新增一个变量
shm_put_var($shm_id, VARIABLE_KEY, 0);
}
exit;
break;
default:
$childs[$pid] = 1;
break;
}
}
// 等待所有子进程的结束
while ($childs) {
$childPid = pcntl_wait($status);
if ($childPid > 0) {
unset($childs[$childPid]);
}
}
// 父进程读取共享内存中的变量
echo 'val = ' . shm_get_var($shm_id, VARIABLE_KEY) . PHP_EOL;
// 删除指定共享内存的所有数据
shm_remove($shm_id);
// 关闭和指定共享内存的连接
shm_detach($shm_id);
运行结果:
[root@e2963c647c8b www]# php shm-problem.php
val = 0
分析:
由于三个子进程sleep(), 所以三个子进程读到的信息都是共享内存中不存在值, 所以三个进程往共享内存中写值为0
这三个进程发生了资源抢占问题, 这是不合理的.
代码:
<?php
// 创建一个共享内存
$key = ftok(__FILE__, 'a');
$shm_id = shm_attach($key, 1024, 0666);
// 存储子进程的进程ID
$childs = [];
// variable_key
const VARIABLE_KEY = 99;
// 创建信号量
$sem_id = ftok(__FILE__, 'a');
$semaphore = sem_get($sem_id);
// 创建3个子进程
for ($i = 0; $i < 3; $i++) {
$pid = pcntl_fork();
switch ($pid) {
case -1:
die('fork failed');
break;
case 0:
// 获得信号量
sem_acquire($semaphore);
// 共享内存中存在值, +1
if (shm_has_var($shm_id, VARIABLE_KEY)) {
$val = shm_get_var($shm_id, VARIABLE_KEY);
$val++;
// 睡眠
sleep(rand(1, 3));
// 在共享内存中更新一个变量
shm_put_var($shm_id, VARIABLE_KEY, $val);
}
// 共享内存中不存在值, 初始化为0
else {
// 睡眠
sleep(rand(1, 3));
// 在共享内存中新增一个变量
shm_put_var($shm_id, VARIABLE_KEY, 0);
}
// 释放信号量
sem_release($semaphore);
exit;
break;
default:
$childs[$pid] = 1;
break;
}
}
// 等待所有子进程的结束
while ($childs) {
$childPid = pcntl_wait($status);
if ($childPid > 0) {
unset($childs[$childPid]);
}
}
// 父进程读取共享内存中的变量
echo 'val = ' . shm_get_var($shm_id, VARIABLE_KEY) . PHP_EOL;
// 删除指定共享内存的所有数据
shm_remove($shm_id);
// 关闭和指定共享内存的连接
shm_detach($shm_id);
运行结果:
[root@e2963c647c8b www]# php shm-solution.php
val = 2