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

入门例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php 
$shm_key = ftok(__FILE__, 't');

/*
开辟一块共享内存
int $key , string $flags , int $mode , int $size
$flags:
1. a:访问只读内存段
2. c:创建一个新内存段,或者如果该内存段已存在,尝试打开它进行读写
3. w:可读写的内存段
4. n:创建一个新内存段,如果该内存段已存在,则会失败
$mode: 八进制格式 0655
$size: 开辟的数据大小 字节
*/
$shm_id = shmop_open($shm_key, "c", 0655, 10240);
// 写入数据 数据必须是字符串格式 , 最后一个指偏移量
$size = shmop_write($shm_id, 'sui is very good~', 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 格式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$key = ftok(__FILE__, 'a');
$shar_key = 666;

// 创建一个共享内存
$shm_id = shm_attach($key, 1024, 0666); // resource type


# 设置一个值
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 型的参数。

实际运用

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php

//共享内存通信

//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);

最终的 count 的值还是0。这是为什么呢?简单分析一下,当我们开启创建进程的时候,3个子进程同时打开了 共享内存区域,此时他们几乎是同步的,所以读到的信息都是没有count值,此时他们执行自己的业务

逻辑然后将 count 为0的结果写入内存区域。这并不是我们想要的结果,三个子进程互相抢占了资源,这是不合理的,那怎么规避这个问题呢?答案是通过 信号量 !

信号量

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 <?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);

解决通信问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

//共享内存通信

//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);

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

纵有疾风起,人生不言弃!