真正的PHP7多线程:如何重建PHP并使用pthreads


其实你的PHP应用真的可以通过多线程的支持来并行运行多任务,但是你知道PHP的构建过程非常的繁琐和耗时--不仅仅如此,迁移一个生产环境的PHP服务到新的PHP版本也听起来非常的头疼。但是,如果我们使用一些实用的程序来协助处理上述的工作,事情便不会那么糟糕了。

本文将引导你完成编译一个新的、支持多线程的PHP版本的编译的过程,然后在迁移到该版本之前,演示如何使用pthreads扩展来编写多线程应用程序。

从包管理器中构建现成的PHP版本是不支持多线程的,我们需要做的是使用一个启用ZTS或“Zend线程安全”的标志重新编译PHP。然后允许我们展开对多线程的使用。为了简化这个编译过程,我们将使用一个名为phpbrew的工具,该工具设计用于编译多个版本的php并在它们之间轻松切换。一旦我们完成这个编译,我们就可以使用pthreads进行试验了。

有一些特定于平台的坑,但我们将通过简单的解决方案来克服这些问题。

 现在假设你已经有一台开发机器以及生产就绪的服务器,我们将在Mac OS和CentOS上完成phpbrew构建过程。如果你使用的是另一个发行版,我将突出显示用于特定OS需求的资源。 让我们先从在Mac上开始构建PHP。

phpbrew简介

phpbrew项目起始于2012年,直到今天还在被不断地改进—一些忠实的维护者证明了PHP仍然是许多开发人员的关键工具。phpbrew的安装和使用说明条理清晰,易于理解。虽然不是必需的,但是此刻熟悉一下phpbrew是有好处的。

为什么使用phpbrew而不是普通的构建命令?

有几个原因。phpbrew不仅可以构建多个版本的php,并允许我们在它们之间轻松切换,而且使用“变体”还可以使构建过程更加容易。我们可以使用充当一个标志或一组标志的关键字,而不是像在普通构建命令中那样复制许多标志。一个变体的例子是+mb,它将使用mbstringmbregex构建PHP。直接转到这里的变体列表,查看可用的变体。

除了变体之外,我们还可以安装扩展,并只使用一个命令配置php.ini文件。换句话说,我们不必担心在安装扩展-phpbrew之后维护php.ini配置文件--phpbrew已经自动为我们做了。例如,我们将使用以下命令将pthreads安装到新编译的php版本中:

phpbrew ext install github:krakjoe/pthreads

正如你所看到的,我们正在直接从Github安装pthreads扩展的最新版本(重要!),并将其包含在配置文件中,所有这些都来自这个命令。很好。这是phpbrew提供的安装扩展支持的一部分。

他们的Github项目主页链接如下:

在Mac OS上使用phpbrew

让我们首先安装phpbrew。在终端内,使用以下命令下载和安装phpbrew:

curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
chmod +x phpbrew
sudo mv phpbrew /usr/local/bin/phpbrew

在将phpbrew移到您的本地bin之后,为你的shell环境初始化一个bash脚本:

phpbrew init
[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc

现在可以运行下面的命令更新PHP的编译了:

phpbrew known
phpbrew update

我们将构建最新版本的php7.2,即本文撰写时的7.2.12。如果你想使用当前最新版本,请随意使用。 此时,你还可以运行phpbrew variants以获得可供你使用的完整变体列表。phpbrew现在可以使用了,现在只需要一个命令来构建php。但是我们还有一件事情要做--安装PHP依赖项。

Mac:安装PHP依赖项

php有很多依赖,phpbrew已经专门编写了一整页文档来概述这些要求的平台特定安装说明。 我们现在使用的是Mac,根据上述要求页面的Mac OS部分,我们可以使用HomeBrew或MacPorts来安装它们。 

 接受我的建议,使用MacPorts。我第一次(也是急切地)尝试使用HomeBrew,但是遇到了一致的链接失败和构建错误,而且看不到解决方案。如果尚未安装MacPorts,请从MacPorts下载页面获取操作系统的最新版本(此时10.14 mojave是最新版本)。安装后,运行以下命令:

port install curl automake autoconf icu depof:php72 depof:php72-gd mcrypt bison re2c gettext openssl

其中一些可能已经安装在你的系统上。这不是问题-只需运行整个命令,让安装程序完成其余操作。

Mac: 编译PHP

我们现在已经准备好用启用ZTS的方法构建PHP。运行以下phpbrew安装命令:

phpbrew install php-7.2.12 +default -- --enable-maintainer-zts

这里我们使用 +default,其中包括常用的PHP扩展。我们也在用普通标志扩展我们的变体,这些标志可以用 --flag1--flagN 附加到命令中。 此过程可能需要一段时间,具体取决于你的机器。在我测试过的第一代12英寸MacBook上,构建只需30分钟就完成了。生成日志是在你可以跟踪的情况下提供的,它会给你实时反馈。 一旦构建完成,我们将收到构建所在位置的输出,以及我们的ini配置文件位置。非常方便。

Mac:使用新的PHP版本

此时,你可以运行 phpbrew list,列出所有的php构建。你将注意到该列表中的php-7.2.12-刚刚构建的版本。 phpbrew还提供了switchuse,并根据我们的构建使用命令来切换php版本。像这样永久地切换到新版本:

phpbrew use php-7.2.12

在你现在所在的终端窗口中,php命令将始终引用新构建的PHP7.2。

Mac: 安装pthreads

我们的最后一项工作是安装pthreads扩展。使用以下命令执行此操作:

phpbrew ext install github:krakjoe/pthreads

让我们考虑一些你可能希望安装的其他扩展。我在开发环境中使用了composer和mongodb,以及xdebug。要安装这些扩展,请运行以下命令:

phpbrew ext install mongodb
phpbrew ext install xdebug stable
phpbrew app get composer

如果您需要任何其他扩展,请继续使用 phpbrew ext install 以获得您所需要的最新构建。 现在我们可以在Mac上使用pthreads了。在下一节中,我们将在CentOS上运行整个安装过程。如果不需要进一步安装,请跳到pthreads部分!

在Cent OS7上使用phpbrew

让我们先安装在CentOS上构建PHP的依赖项。使用以下命令安装依赖:

sudo yum install make automake gcc gcc-c++ kernel-devel
sudo yum install php php-devel php-pear bzip2-devel yum-utils bison re2c libmcrypt-devel libpqxx-devel libxslt-devel pcre-devel libcurl-devel libgsasl-devel openldap-devel, httpd-devel

我还需要在我的CentOS系统上安装readline:

yum install readline-devel

如果你的CentOS服务器上没有安装PHP,phpbrew要求建议你首先安装它。我更喜欢使用RPM来安装PHP。使用以下命令执行此操作:

sudo yum -y install epel-release
wget http://rpms.famillecollet.com/enterprise/remi-release-7.rpm
rpm -Uvh remi-release-7*.rpm

更新php7.2 remi文件以启用php7.2。仅在[REMI-PHP7.2]块中将 enabled=0更改为 enabled=1

cd /etc/yum.repos.d/
#open php7.2 repo file and change enabled=0 to enabled=1
sudo vi remi-php72.repo

最后,使用yum安装以下PHP包:

sudo yum install php php-gd php-common php-mysql php-mcrypt php-devel php-xml

CentOS: 安装phpbrew

安装phpbrew与在Macs是相同命令。为了完整起见,这里再次列出了安装命令:

curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
chmod +x phpbrew
sudo mv phpbrew /usr/local/bin/phpbrew
phpbrew init
[[ -e ~/.phpbrew/bashrc ]] && source ~/.phpbrew/bashrc
phpbrew known
phpbrew update

值得注意的是,如果重新启动服务器,则需要使用phpbrew init重新初始化phpbrew,并再次修改bashrc。最好是在引导时设置一个守护进程,以便完成phpbrew的自启动。

CentOS: 编译PHP

build命令与mac命令略有不同。在CentOS上,用以下方法构建PHP:

phpbrew install php-7.2.12 +default +openssl=/usr -- --enable-maintainer-zts --with-libdir=lib64

我们现在使用的是 +openssl=/usr 变量和附加的--with-libdir=lib64标志。为什么会这样?因为与mac构建不同,我们显式地需要为要编译的php构建定义openssl位置。如果没有,我们将在构建过程中得到一个错误,说明没有找到openssl库。这个疏忽需要一个简单的修复,但是如果你想调查它,这个问题已经在Github上讨论过了。

 有关在VPS上编译PHP的内存需求的说明:如果你使用的是VPS,请确保至少有1GB的可用内存。我尝试在大约400MB的空闲内存上编译PHP,但内存耗尽,因此构建失败。如果需要,请临时升级您的VPS资源以编译PHP,然后在完成后恢复。

CentOS: 使用新的编译版本

有了一个成功的新构建,我们现在可以与Mac上相同的方式使用它:

phpbrew use php-7.2.12

CentOS: 安装 pthreads

安装pthreads扩展-和其他感兴趣的扩展--也与Mac相同:

phpbrew ext install github:krakjoe/pthreads
phpbrew ext install mongodb
phpbrew ext install xdebug stable
phpbrew app get composer

完成这一部分后,我们现在有了一个适合本地机器和生产环境上多线程处理的PHP版本了!最后一个难题是使用pthreads来实现我们新发现的线程功能。

使用pthreads

pthreads给PHP的使用者带来了现在认为在其他语言中是理所当然的线程功能。它提供了与基于POSIX线程的PHP兼容的多线程。这是其核心的真正多线程。 那么我们如何使用pthreads呢?它提供了一个继承于Threads的类。从这里开始,我们使用线程的run()方法对希望在单独的线程中执行的逻辑进行编码。象下面这样:

class MyProgram extends Thread {
   public function run() {
      //run my program
      echo "Woo, I am running in a new thread!";
   }
}
$program1 = new MyProgram;
$program1->start() && $program1->join();
echo 'Thread finished';

这里我们使用两个pthread方法start() join(),它们是从Thread继承的。

  start()是不言自明的-它在单独的线程中调用run()方法,允许开始并行处理。 

 另一方面,join() 需要更多的解释:

  join() 允许我们将一个线程引入当前上下文,因此在当前上下文继续执行之前,它将等待线程完成执行。 换句话说,如果我们将一个线程加入到我们的主进程中,那么主进程将等待我们加入的线程完成,然后再继续执行。 

 在上面的代码片段中,$program1的线程被加入到我们的主进程中,因此在线程完成处理后调用最后一条echo语句。

示例1:连续连接线程

要了解这里发生了什么,请考虑下面的示例,其中5个线程作业是按顺序实例化、启动和联接的。每个线程进程休眠x秒(x是线程索引),然后回送结束语句:

因为我们要在每个线程启动时将其引入上下文中,所以执行将等到所述线程完成执行后再转到下一个线程。 在phpbrew终端窗口中,使用php multi-thread1.php运行上面的脚本来检查这种行为。

示例2:同时运行线程 

 让我们采用与上面相同的类和行为,但这次不要在每个线程开始时连接它们。 在本例中,所有5个线程在循环中同时启动,join() 方法被删除。当线程在后台完成处理时,我们的主执行将休眠5秒钟。然后我们将再次尝试加入线程。运行以下示例检查行为:

这里需要注意几个有趣的问题: 

 即使线程在不同的线程中同时运行,我们仍然在终端中接收它们的echo语句。 

一旦线程完成执行-失败,我们将尝试加入线程。正如上一条echo语句所演示的那样,不是PHP抛出错误,而是在主进程上继续执行。

 线程可以在命名空间类中使用吗?  

是的,它们确实可以。例如,如果你使用的是composer autoloading,那么你可以根据前面的示例使用extends\Thread简单地扩展类。

探索pthreads 

探索pthreads库的最佳方法是通过php.net文档。在编写时,pthreads总共包含9个类和53个方法。实际上,你可能不需要使用其中的大部分。实际上,如果你只是希望通过运行并行任务来加速执行,那么上面示例中使用的方法就足够了。然而,它总是值得你熟悉一个包的功能,以供将来参考。

查看pthreads文档索引以开始。这里列出的所有方法都有示例,如:Thread::getCurrenThreadId-一个可以在线程的run()方法中调用以获取其ID的标识函数。 线程类包含有价值的状态检测方法,例如 isRunning,这是另一个有用的方法,允许我们检查run()方法是否在特定线程的给定时间执行。

结论

现在你应该将PHP应用程序升级到多线程了。解锁这个多线程的功能并看到脚本执行得更快是非常令人满意的。 花些时间以最大化速度的方式设计线程,但要以干净的顺序执行任务。一些线程依赖于其他线程的完成吗?如果是这样,请跟踪你的执行状态。记住,线程增加了灵活性和速度,但它们也增加了应用程序的复杂性。 你是如何使用pthreads的呢?很高兴能听到你的观点!

本文翻译自:https://medium.com/@rossbulat/true-php7-multi-threading-how-to-rebuild-php-and-use-pthreads-bed4243c0561

感谢原作者的创作!