前言

PHP开发的小伙伴很少接触到多进程的概念。

当然,随着对`计算机及操作系统的熟悉和学习,进程自然需要我们去掌握TA。

PHP也是有多进程的操作的,我们可以通过下面的命令查看PHP是否可以创建多进程:

php -m |grep pcntl

如果输出 pcntl 的话则说明支持。

当然,PHP也有其它创建多进程的方法,本篇文章讲解的是利用pcntl扩展来创建多进程。

在使用pcntl创建多进程之前,我们先熟悉下进程的概念。

什么是进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

上面是百度百科给出的解释。

其中我们需要关注一句话 进程是系统进行资源分配和调度的基本单位。这句话是非常重要的。早期的计算机是单任务操作系统,也就意味着同一时间,计算机只能运行一个进程。到后台的多任务操作系统,结合多核CPU,计算机可以同时运行好几个进程。

为什么引入多进程?

答案很明显,是为了提高计算机的利用效率。

进程的状态

image.png

对于进程的状态,我们也需要做到心中有数。当然,本篇文章不会细细的阐述有关进程的内容。有兴趣的小伙伴可以搜索相关资料深入阅读。

PHP创建多进程

创建进程

上述的关于进程的基础知识简单的从其它网站复制粘贴过来,接下来,进入本篇文章的主题,咋们来创建下多进程。

<?php

$i = 3;

while ($i > 0) {
    $i--;
    $pid = pcntl_fork();
    if ($pid === -1) {
        exit('无法创建子进程');
    } elseif ($pid > 0) {
        // 父进程
        $pid = getmypid();
        echo "我是父进程{$pid}\n";
    } else {
        // 这里是子进程
        $pid = getmypid();
        echo "我是子进程{$pid}\n";
        exit;
    }
}

注意,上面的代码只能在php-cli下执行。

我们来分析下代码。首先,一个while循环,这里是循环3次,也就是我们要创建3个进程。

创建进程这里我们用到的是 pcntl_fork() 函数。这个函数的返回值有三种:

返回值说明
-1创建子进程失败
0意味着子进程创建成功,且当前环境已经是子进程的执行环境
>0子进程创建成功,当前环境是父进程

执行 pcntl_fork() 之后,系统会从当前开始,一分为2成为两个进程,一个是父进程,一个是子进程,它们是有亲戚关系在这里的,但是他们的环境(内存空间)从 pcntl_fork() 执行之后开始又是相互独立的。

我们可以利用 pstree 命令来查看下这个层级关系:

pstree -p 84347

输出:

-+= 00001 root /sbin/launchd
 \-+= 00654 xiaoteng /Applications/iTerm.app/Contents/MacOS/iTerm2
   \-+= 00656 xiaoteng /Applications/iTerm.app/Contents/MacOS/iTerm2 --server l
     \-+= 00657 root login -fp xiaoteng
       \-+= 00658 xiaoteng -zsh
         \-+= 84346 xiaoteng php process.php
           \--- 84347 xiaoteng php process.php

上述的脚本输出结果:

我是父进程16791
我是父进程16791
我是子进程16792
我是父进程16791
我是子进程16793
我是子进程16794

输出了六条结果,每执行一次 fork 父进程输出一个结果,子进程输出一个结果(父进程是统一的)。

进程独立

下面,我们来测试下,父进程和子进程是相互独立的:

<?php

$i = 1;

$global = 10;

$pid = pcntl_fork();
if ($pid === -1) {
    exit('无法创建子进程');
} elseif ($pid > 0) {
    // 父进程
    $pid = getmypid();
    echo "我是父进程:{$pid}\n";
    $j = 5;
    echo "父进程:".$global."\n";
    while ($j > 0) {
        $j--;
        $global++;
        echo '父进程:'.$global."\n";
        sleep(1);
    }
} else {
    // 这里是子进程
    $pid = getmypid();
    echo "我是子进程:{$pid}\n";
    $j = 5;
    echo "子进程:".$global."\n";
    while ($j > 0) {
        $j--;
        $global--;
        echo "子进程:".$global."\n";
        sleep(1);
    }
    exit;
}

输出结果:

我是父进程:16900
父进程:10
父进程:11
我是子进程:16901
子进程:10
子进程:9
子进程:8
父进程:12
子进程:7
父进程:13
子进程:6
父进程:14
子进程:5
父进程:15

可以很明显的看到,在fork之前,$global 变量是两个进程共享的,值都为 10fork 之后,$global 各自独立在自己的进程中,赋值操作互不影响。