pcntl_fork是创建了一个子进程,父进程和子进程 都从fork的位置开始向下继续执行,不同的是父进程执行过程中,得到的fork返回值为子进程号,而子进程得到的是0。
基本使用代码如下
$pid = pcntl_fork();
//父进程和子进程都会执行下面代码
if ($pid == -1) {
//错误处理:创建子进程失败时返回-1.
die('could not fork');
} else if ($pid) {
//父进程会得到子进程号,所以这里是父进程执行的逻辑
pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。
} else {
//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。
}
初级使用
多进程,从字面意思就可以看出可以创建多个。使用pcntl_fork我们只创建了一个子进程,想要创建多个进程,我们可以使用循环函数,如下
header("Content-type: text/html; charset=utf-8");
if (strtolower(php_sapi_name()) != 'cli') {
die("请在cli模式下运行");
}
$index = 0;
$loop = 1;
while ($index < $loop) {
echo "当前进程:" . getmypid() . PHP_EOL;
$pid = pcntl_fork(); //fork出子进程
if ($pid == -1) { // 创建错误,返回-1
die('进程fork失败');
} else if ($pid) { // $pid > 0, 如果fork成功,返回子进程id
// 父进程逻辑
pcntl_wait($status); // 父进程必须等待一个子进程退出后,再创建下一个子进程。
$child_id = $pid; //子进程的ID
$pid = posix_getpid(); //获取当前进程Id
$ppid = posix_getppid(); // 进程的父级ID
$time = microtime(true);
echo "我是父进程,fork的子进程id: {$child_id};当前进程id:{$pid};父进程id:{$ppid}; 当前index:{$index}; 当前时间:{$time}".PHP_EOL;
} else { // $pid = 0
// 子进程逻辑
$cid = $pid;
$pid = posix_getpid();
$ppid = posix_getppid();
$myid = getmypid();
$time = microtime(true);
echo "我是子进程,当前进程id:{$pid};父进程id:{$ppid}; 当前index:{$index}; 当前时间:{$time}".PHP_EOL;
//exit;
//sleep(2);
}
$index++;
}
$loop=1时,执行结果如下
当前进程:16604
我是子进程,当前进程id:16605;父进程id:16604; 当前index:0; 当前时间:1528696774.1978
我是父进程,fork的子进程id: 16605;当前进程id:16604;父进程id:15128; 当前index:0; 当前时间:1528696774.2032
pentl_wait函数挂起当前进程的执行直到一个子进程退出或接收到一个信号要求中断当前进程或调用一个信号处理函数。
进程16604执行代码时,调用了pectl_wait($status)被挂起;当他的子进程16605执行完毕后,才继续的wait之后的代码。
$loop=2时,执行结果如下
当前进程:16613
我是子进程,当前进程id:16614;父进程id:16613; 当前index:0; 当前时间:1528696781.4751
当前进程:16614
我是子进程,当前进程id:16615;父进程id:16614; 当前index:1; 当前时间:1528696781.4756
我是父进程,fork的子进程id: 16615;当前进程id:16614;父进程id:16613; 当前index:1; 当前时间:1528696781.4802
我是父进程,fork的子进程id: 16614;当前进程id:16613;父进程id:15128; 当前index:0; 当前时间:1528696781.4858
当前进程:16613
我是子进程,当前进程id:16616;父进程id:16613; 当前index:1; 当前时间:1528696781.4863
我是父进程,fork的子进程id: 16616;当前进程id:16613;父进程id:15128; 当前index:1; 当前时间:1528696781.4913
我们看到,只是多了一次循环,比上次循环多出了2倍的输出,这个就是多进程下代码的执行跟平时不一样,具体执行过程如下
我们看到,循环fork进程时,生成的进程,会参与到下次的循环中,这导致了进程数量的不可控。同时我们看到上面的代码,是按顺序执行的,上一个进程结束后,下个进程才开始,这跟普通的while并没有什么太大区别,属于阻塞执行。
我们使用多进程,就是想要同时执行任务,提高效率。所以我们代码如下修改
while ($index < $loop) {
$pid = pcntl_fork();
if ($pid == -1) {
die('进程fork失败');
} else if ($pid) {
//父进程不参与任务执行,只用作子进程的控制
// WNOHANG参数,如果没有子进程退出立刻返回
//不需要挂起等待子进程退出
pcntl_wait($status,WNOHANG);
} else {
// 子进程逻辑
$cid = $pid;
$pid = posix_getpid();
$ppid = posix_getppid();
$myid = getmypid();
$time = microtime(true);
echo "我是子进程,当前进程id:{$pid};父进程id:{$ppid}; 当前index:{$index}; 当前时间:{$time}".PHP_EOL;
//子进程执行结束后退出,不参与到下次的循环中,每次循环只生成一个子进程
exit;
}
$index++;
}
以上代码通过简单的改造,我们可以明确的控制进程的数量,同时可以同生成多个进程执行任务,提高了效率。
进阶教程
一个进程,包括代码、数据和分配给进程的资源。调用fork()函数后台,系统调用创建一个与原来进程几乎完全相同的进程。
几乎一摸一样,说明还是有些不同的。不同点在于子进程内的数据和资源并不是完全独立的。
通过下面的代码就可以看出
if(pid==0){
num+=10;
printf("child %d\n",num);
printf("child %d\n",&num);
}
if(pid>0){
num+=20;
printf("parent %d\n",num);
printf("parent %d\n",&num);
}
输出
parent 20
parent -948260088
child 10
child -948260088
可以看到,子进程和父进程中的变量指向了同一个空间地址。因为子进程是完全复制父进程的,所以num的地址是一样的,可是子进程在创建时,复制的是虚拟地址空间,只有在写入时,才会是才会分配物理空间。
这是因为Linux的COW(写时拷贝技术)
在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,其对应的物理空间是一个。当父进程或子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间