php的多进程处理依赖于pcntl扩展,通过pcntl_fork创建子进程来进行并行处理。
例1如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?php
$pid = pcntl_fork();
if ( $pid == -1) {
die ( 'fork error' );
} else if ( $pid ) {
echo "parent \n" ;
pcntl_wait( $status );
} else {
echo "child \n" ;
exit ;
}
|
pcntl_fork创建了子进程,父进程和子进程都继续向下执行,而不同是父进程会获取子进程的$pid也就是$pid不为零。而子进程会获取$pid为零。通过if else语句判断$pid我们就可以在指定位置写上不同的逻辑代码。
上述代码会分别输出parent和child。那么输出的parent和child是否会有顺序之分?是父进程会先执行?
例2如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
$pid = pcntl_fork();
if ( $pid == -1) {
die ( 'fork error' );
} else if ( $pid ) {
sleep(3);
echo "parent \n" ;
pcntl_wait( $status );
} else {
echo "child \n" ;
exit ;
}
|
我们在父进程中通过sleep来延缓执行,看看效果。
结果是,很快输出了child,等待了接近3秒后,才输出parent。所以父进程和子进程的执行是相对独立的,没有先后之分。
那么问题又来了?pcntl_wait是做什么用的?
会挂起当前进程,直到子进程退出,如果子进程在调用此函数之前就已退出,此函数会立刻返回。子进程使用的资源将被释放。
例3如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<?php
$pid = pcntl_fork();
if ( $pid == -1) {
die ( 'fork error' );
} else if ( $pid ) {
pcntl_wait ( $status );
echo "parent \n" ;
} else {
sleep(3);
echo "child \n" ;
exit ;
}
|
上述代码,我们可以看到,父进程执行pcntl_wait时就已经挂起,直到等待3秒后输出child,子进程退出后。父进程继续执行,输出parent。
例4如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<?php
define( 'FORK_NUMS' , 3);
$pids = array ();
for ( $i = 0; $i < FORK_NUMS; ++ $i ) {
$pids [ $i ] = pcntl_fork();
if ( $pids [ $i ] == -1) {
die ( 'fork error' );
} else if ( $pids [ $i ]) {
pcntl_waitpid( $pids [ $i ], $status );
echo "pernet \n" ;
} else {
sleep(3);
echo "child id:" . getmypid () . " \n" ;
exit ;
}
}
|
上述代码,我们创建3个子进程,父进程分别挂起等待子进程结束后,输出parent。
输出结果如下:
child id:19090
pernet
child id:19091
pernet
child id:19092
pernet
例5如下:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<?php
define( 'FORK_NUMS' , 3);
$pids = array ();
for ( $i = 0; $i < FORK_NUMS; ++ $i ) {
$pids [ $i ] = pcntl_fork();
if ( $pids [ $i ] == -1) {
die ( 'fork error' );
} else if ( $pids [ $i ]) {
} else {
sleep(3);
echo "child id:" . getmypid () . " \n" ;
exit ;
}
}
foreach ( $pids as $k => $v ) {
if ( $v ) {
pcntl_waitpid( $v , $status );
echo "parent \n" ;
}
}
|
输出结果如下:
child id:19118
child id:19119
child id:19120
parent
parent
parent
为什么上述代码跟例4的输出结果不一样?
我们可以看到例5的pcntl_waitpid函数放在了foreach中,foreach代码是在主进程中,也就是父进程的代码中。当执行foreach时,可能子进程已经全部执行完毕并退出。pcntl_waitpid会立刻返回,连续输出三个parent。
(*在子进程中,需通过exit来退出,不然会产生递归多进程,父进程中不需要exit,不然会中断多进程。)
例6如下:
?
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
|
<?php
define( 'FORK_NUMS' , 3);
$pids = array ();
$fp = fopen ( './test.log' , 'wb' );
$num = 1;
for ( $i = 0; $i < FORK_NUMS; ++ $i ) {
$pids [ $i ] = pcntl_fork();
if ( $pids [ $i ] == -1) {
die ( 'fork error' );
} else if ( $pids [ $i ]) {
} else {
for ( $i = 0; $i < 5; ++ $i ) {
flock ( $fp , LOCK_EX);
fwrite( $fp , getmypid () . ' : ' . date ( 'Y-m-d H:i:s' ) . " : {$num} \r\n" );
flock ( $fp , LOCK_UN);
echo getmypid (), ": success \r\n" ;
++ $num ;
}
exit ;
}
}
foreach ( $pids as $k => $v ) {
if ( $v ) {
pcntl_waitpid( $v , $status );
}
}
fclose( $fp );
|
代码如上:我们创建三个子进程,来同时向test.log文件写入内容,test.log内容如下:
19507 : 2016-03-16 20:40:52 : 1
19507 : 2016-03-16 20:40:52 : 2
19507 : 2016-03-16 20:40:52 : 3
19507 : 2016-03-16 20:40:52 : 4
19507 : 2016-03-16 20:40:52 : 5
19509 : 2016-03-16 20:40:52 : 1
19509 : 2016-03-16 20:40:52 : 2
19509 : 2016-03-16 20:40:52 : 3
19509 : 2016-03-16 20:40:52 : 4
19509 : 2016-03-16 20:40:52 : 5
19508 : 2016-03-16 20:40:52 : 1
19508 : 2016-03-16 20:40:52 : 2
19508 : 2016-03-16 20:40:52 : 3
19508 : 2016-03-16 20:40:52 : 4
19508 : 2016-03-16 20:40:52 : 5
我们可以看到三个子进程的pid,它们分别执行了5次,时间几乎是在同时。但是$num的值并没像我们期望的那样从1-15进行递增。子进程中的变量是各自独立的,互不影响。子进程会自动复制父进程空间里的变量。
如何在进程中共享数据?
我们通过php的共享内存函数shmop来实现。
?
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
|
<?php
define( 'FORK_NUMS' , 3);
$pids = array ();
$fp = fopen ( './test.log' , 'wb' );
$num = 1;
$shmKey = 123;
$shmId = shmop_open( $shmKey , 'c' , 0777, 64);
shmop_write( $shmId , $num , 0);
for ( $i = 0; $i < FORK_NUMS; ++ $i ) {
$pids [ $i ] = pcntl_fork();
if ( $pids [ $i ] == -1) {
die ( 'fork error' );
} else if ( $pids [ $i ]) {
pcntl_waitpid( $pids [ $i ], $status );
} else {
$num = shmop_read( $shmId , 0, 64);
for ( $i = 0; $i < 5; ++ $i ) {
fwrite( $fp , getmypid () . ' : ' . date ( 'Y-m-d H:i:s' ) . " : {$num} \r\n" );
echo getmypid (), ": success \r\n" ;
$num = intval ( $num ) + 1;
}
shmop_write( $shmId , $num , 0);
exit ;
}
}
shmop_delete( $shmId );
shmop_close( $shmId );
fclose( $fp );
|
上述代码的运行结果如下:
19923 : 2016-03-17 00:05:18 : 1
19923 : 2016-03-17 00:05:18 : 2
19923 : 2016-03-17 00:05:18 : 3
19923 : 2016-03-17 00:05:18 : 4
19923 : 2016-03-17 00:05:18 : 5
19924 : 2016-03-17 00:05:18 : 6
19924 : 2016-03-17 00:05:18 : 7
19924 : 2016-03-17 00:05:18 : 8
19924 : 2016-03-17 00:05:18 : 9
19924 : 2016-03-17 00:05:18 : 10
19925 : 2016-03-17 00:05:18 : 11
19925 : 2016-03-17 00:05:18 : 12
19925 : 2016-03-17 00:05:18 : 13
19925 : 2016-03-17 00:05:18 : 14
19925 : 2016-03-17 00:05:18 : 15
这样我们就在进程间共享了$num的数据。