随着互联网的发展,以及PHP快速开发的特点,现在越来越来越多的团队将PHP作为服务端的编程语言。今天PHP7发布,PHP7效率大幅提升,相信后面会有更多的团队使用PHP作为后端服务开发的首选语言。

PHP是单线程的,但是使用PCNTL和POSIX等扩展能实现多进程编程,相比于多线程编程,多进程编程要相对容易很多。在使用PHP开发服务端时,很多时候避免不了和多进程打交道,想着边学边写几篇关于PHP服务端开发的系列博客。个人才疏学浅,有疏漏,还请各位看官指正。

今天我们就从PHP创建守护进程开始说起。

PHP创建守护进程

开始之前,请确认已安装扩展pcntl和posix。请使用

1
$ php -m

查看相关扩展是否安装,如果已安装,列表应该会显示pcntl和posix两行。

创建守护进程就是让进程脱离终端,独自在后台运行,我们可以让父进程创建一个子进程,然后退出父进程,子进程独自在后台运行,这样就实现了一个守护进程。

创建子进程可以使用pcntl扩展中的pcntl_fork函数

1
2
3
4
5
6
7
8
9
10
11
<?php
$pid = pcntl_fork();
if (-1 == $pid) {
// 创建进程失败
exit();
} elseif ($pid) {
// 这里是父进程
exit();
} else {
// 这里是子进程
}

为了使子进程获得最大的权限,我们在创建子进程之前可以使用umask函数,避免一些权限问题,具体的umask作用参见:
php umask(0) what is the purpose

fork后,我们需要使用posix_setsid函数使子进程成为session leader。

现在我们的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
umask(0);
$pid = pcntl_fork();
if (-1 == $pid) {
// 创建进程失败
exit();
} elseif ($pid) {
// 这里是父进程
exit();
} else {
// 这里是子进程
posix_setsid(); // 使当前进程成为session leader
}

在某些情况下,当我们fork一次以后立即退出,并不能保证子进程能够脱离终端,在我们退出终端的时候,仍然能导致子进程终止,服务退出,所以我们需要fork两次以确保这种情况不会发生,stackoverflow上有篇文章解释了为什么fork两次:
What is the reason for performing a double fork when creating a daemon?

fork两次以后,我们的代码如下:

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
<?php
umask(0);
$pid = pcntl_fork();
if (-1 == $pid) {
// 创建进程失败
exit();
} elseif ($pid) {
// 这里是父进程
exit();
}

posix_setsid(); // 使当前进程成为session leader

// 这里是第一个子进程
$pidAgain = pcntl_fork();
if (-1 == $pidAgain) {
// 创建进程失败
exit();
} elseif ($pidAgain) {
// 这里是第二个子进程
exit();
}
// 这里是第三个子进程,即最终得到的守护进程
// 在这里处理任务

这样我们的守护进程就创建完毕,接下来我们把代码完善下。

我们创建一个Server类,模拟启动一个守护进程,在后台处理任务。

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
<?php
class Server
{
/**
* 启动服务.
*
* @return void
*/
public function start()
{
$this->deamon(); // 守护进程化
$this->handleTask(); // 开始处理任务
}

/**
* 使服务守护进程化.
*
* @return void
*/
protected function deamon()
{
umask(0); // 为后面的子进程让出最大权限
$pid = pcntl_fork();
if (-1 == $pid) {
exit("创建子进程失败" . PHP_EOL);
} elseif ($pid) {
exit();
}

posix_setsid(); // 使当前进程成为session leader

$pidAgain = pcntl_fork();
if (-1 == $pidAgain) {
exit("再次创建子进程失败" . PHP_EOL);
} elseif ($pidAgain) {
exit("服务已启动....." . PHP_EOL);
}

}

/**
* 处理请求.
*
* @return void
*/
protected function handleTask()
{
while (true) {
// process task
sleep(2); // 模拟处理请求
}
}

}

$server = new Server();
$server->start(); // 开始服务

在终端执行:

1
$ php scriptName.php

你会看到如下图所示的提示:

终端执行:

1
$ ps -ef | grep php

可以看到在后台运行的守护进程:

进程就这么在后台运行着?
现在我们只有通过shell命令来杀掉它:

1
$ sudo kill pid

下次说下PHP的进程间通信。