我找到了这个名为 threads 的 PECL 包,但还没有发布。PHP 网站上什么也没有出现。
13 回答
来自pthreads扩展的 PHP 手册:
pthreads 是一个面向对象的 API,它允许 PHP 中的用户级多线程。它包括创建针对 Web 或控制台的多线程应用程序所需的所有工具。PHP 应用程序可以创建、读取、写入、执行以及与线程、Worker 和 Stackables 同步。
这听起来令人难以置信,但这是完全正确的。今天,PHP 可以为那些希望尝试它的人提供多线程。
PHP4 的第一个版本,2000 年 5 月 22 日,PHP 带有线程安全架构 - 一种在多线程 SAPI(服务器 API)环境中在单独线程中执行其解释器的多个实例的方法。在过去的 13 年中,这种架构的设计得到了维护和改进:从那时起,它一直在世界上最大的网站上投入生产使用。
用户空间中的线程从来不是 PHP 团队关心的问题,今天仍然如此。您应该了解,在 PHP 开展业务的世界中,已经有一种定义的扩展方法 - 添加硬件。PHP 存在多年以来,硬件变得越来越便宜,因此 PHP 团队越来越不关心这个问题。虽然它变得更便宜,但它也变得更强大。今天,我们的手机和平板电脑具有双核和四核架构以及大量的 RAM,我们的台式机和服务器通常具有 8 或 16 核、16 和 32 GB 的 RAM,尽管我们可能并不总是能够拥有两个在预算范围内,拥有两个桌面对我们大多数人来说很少有用。
此外,PHP 是为非程序员编写的,它是许多爱好者的母语。PHP 如此容易被采用的原因是因为它是一种易于学习和编写的语言。PHP 在今天如此可靠的原因在于它的设计投入了大量的工作,以及 PHP 团队做出的每一个决定。这么多年过去了,它的可靠性和卓越的性能使它成为人们关注的焦点。它的竞争对手已经落入时间或压力之下。
多线程编程对大多数人来说并不容易,即使有最连贯和可靠的 API,也有不同的事情需要考虑,也有很多误解。PHP 小组不希望用户空间多线程成为核心功能,它从未受到过认真的关注——这是正确的。对于每个人来说,PHP 不应该很复杂。
考虑到所有因素,允许 PHP 使用它的生产就绪和经过测试的特性仍然可以带来好处,从而可以充分利用我们所拥有的,当添加更多并不总是一种选择时,而且很多的任务从来都不是真正需要的。
pthreads 为那些希望探索它的人实现了一个 API,它允许用户对 PHP 应用程序进行多线程处理。它的 API 在很大程度上是一项正在进行的工作,并指定了 beta 级别的稳定性和完整性。
众所周知,PHP 使用的某些库不是线程安全的,程序员应该清楚 pthreads 无法更改这一点,并且不会尝试尝试。但是,任何线程安全的库都是可用的,就像在解释器的任何其他线程安全设置中一样。
pthreads 利用 Posix 线程(即使在 Windows 中),程序员创建的是真正的执行线程,但要使这些线程有用,他们必须了解 PHP——能够执行用户代码、共享变量并允许有用的通信方式(同步)。所以每个线程都是用解释器的一个实例创建的,但根据设计,它的解释器与解释器的所有其他实例隔离 - 就像多线程服务器 API 环境一样。pthreads 试图以理智和安全的方式弥合差距。C 中线程程序员的许多担忧对于 pthreads 程序员来说并不存在,根据设计,pthreads 是读取时复制和写入时复制(RAM 很便宜),因此没有两个实例会操作相同的物理数据,但它们都可以影响另一个线程中的数据。
为什么读取时复制和写入时复制:
public function run() {
...
(1) $this->data = $data;
...
(2) $this->other = someOperation($this->data);
...
}
(3) echo preg_match($pattern, $replace, $thread->data);
(1) 当 pthreads 对象数据存储上持有读写锁时,数据会从其在内存中的原始位置复制到对象存储。pthreads 不会调整变量的引用计数,如果没有进一步的引用,Zend 能够释放原始数据。
(2) someOperation 的参数引用对象存储,存储的原始数据,它本身是 (1) 的结果的副本,被引擎再次复制到 zval 容器中,同时发生读锁被持有对象存储,锁被释放,引擎可以执行该功能。创建 zval 时,它的引用计数为 0,使引擎能够在操作完成时释放副本,因为不存在对它的其他引用。
(3) preg_match 的最后一个参数引用了数据存储,获得了一个读锁,将(1)中的数据集复制到一个 zval,再次使用 refcount 为 0。锁被释放,对 preg_match 的调用操作数据的副本,它本身就是原始数据的副本。
要知道的事情:
存储数据的对象存储的哈希表是线程安全的,它
基于 Zend 随 PHP 提供的 TsHashTable。对象存储有一个读写锁,为 TsHashTable 提供了一个额外的访问锁,这样如果需要(它确实,var_dump/print_r,直接访问属性,因为 PHP 引擎想要引用它们)pthreads 可以操作 TsHashTable在定义的 API 之外。
只有在复制操作发生时才会持有锁,当复制完成时,锁会以合理的顺序被释放。
这表示:
当发生写入时,不仅会持有读写锁,还会持有额外的访问锁。表本身被锁定,其他上下文不可能锁定、读取、写入或影响它。
当发生读取时,不仅读取锁被持有,而且附加的访问锁也被持有,表再次被锁定。
没有两个上下文可以物理地或同时访问对象存储中的相同数据,但是在任何具有引用的上下文中进行的写入将影响在具有引用的任何上下文中读取的数据。
这是无共享架构,唯一存在的方式是共存。那些有点精明的人会看到,这里有很多抄袭,他们会怀疑这是否是一件好事。相当多的复制在动态运行时中进行,这就是动态语言的动态。pthreads 是在对象级别实现的,因为可以对一个对象进行良好的控制,但是方法——程序员执行的代码——有另一个上下文,没有锁定和副本——本地方法范围。在 pthreads 对象的情况下,对象范围应被视为在上下文之间共享数据的一种方式,这就是它的目的。考虑到这一点,您可以采用技术来避免锁定对象存储,除非有必要,
大多数可用于 PHP 的库和扩展都是围绕 3rd 方的瘦包装器,PHP 核心功能在某种程度上是相同的。pthreads 不是 Posix 线程的薄包装;它是一个基于 Posix Threads 的线程 API。在 PHP 中实现用户不理解或无法使用的线程是没有意义的。没有理由不知道互斥锁是什么或做什么的人不应该在技能和资源方面利用他们所拥有的一切。一个对象的功能就像一个对象,但是在两个上下文可能发生冲突的地方,pthreads 提供了稳定性和安全性。
任何在 java 中工作过的人都会看到 pthreads 对象和 java 中的线程之间的相似之处,这些人无疑会看到一个名为 ConcurrentModificationException 的错误——因为如果两个线程写入相同的物理数据,这听起来是由 java 运行时引发的错误同时。我理解它为什么存在,但让我感到困惑的是,资源如此便宜,再加上运行时能够在它选择的确切且唯一的时间为用户实现安全性时检测到并发性在运行时抛出一个可能致命的错误,而不是管理执行和对数据的访问。
pthreads 不会发出这种愚蠢的错误,我相信,编写 API 是为了使线程尽可能稳定和兼容。
多线程不像使用新数据库,应该密切注意手册中的每个单词和 pthread 附带的示例。
最后,来自 PHP 手册:
pthreads 曾经是,现在也是,结果非常好。它的任何限制或功能可能随时更改;这就是实验的本质。它的限制——通常是由实施强加的——是有充分理由存在的;pthreads 的目的是为任何级别的 PHP 多任务提供可用的解决方案。在 pthreads 执行的环境中,为了提供一个稳定的环境,一些限制和限制是必要的。
以下是 Wilco 建议的示例:
$cmd = 'nohup nice -n 10 /usr/bin/php -c /path/to/php.ini -f /path/to/php/file.php action=generate var1_id=23 var2_id=35 gen_id=535 > /path/to/log/file.log & echo $!';
$pid = shell_exec($cmd);
基本上这会在命令行执行 PHP 脚本,但会立即返回 PID,然后在后台运行。(echo $! 确保除了 PID 之外不返回任何其他内容。)这允许您的 PHP 脚本在需要时继续或退出。当我使用它时,我已将用户重定向到另一个页面,每隔 5 到 60 秒就会进行一次 AJAX 调用以检查报告是否仍在运行。(我有一个表来存储 gen_id 和与之相关的用户。)检查脚本运行以下内容:
exec('ps ' . $pid , $processState);
if (count($processState) < 2) {
// less than 2 rows in the ps, therefore report is complete
}
这里有一篇关于这种技术的简短文章:http: //nsaunders.wordpress.com/2007/01/12/running-a-background-process-in-php/
我知道没有可用的东西。下一个最好的办法是让一个脚本通过 CLI 执行另一个脚本,但这有点初级。根据您要做什么以及它的复杂程度,这可能是也可能不是一个选项。
简而言之:是的,php 中有多线程,但您应该改用多处理。
背景信息:线程与进程
关于线程和进程的区别总是有点混乱,所以我将简要描述两者:
- 线程是 CPU 将处理的一系列命令。它包含的唯一数据是程序计数器。每个 CPU 内核一次只能处理一个线程,但可以通过调度在不同线程的执行之间切换。
- 进程是一组共享资源。这意味着它由内存、变量、对象实例、文件句柄、互斥体、数据库连接等的一部分组成。每个进程还包含一个或多个线程。同一进程的所有线程共享其资源,因此您可以在一个线程中使用您在另一个线程中创建的变量。如果这些线程是两个不同进程的一部分,那么它们就不能直接访问彼此的资源。在这种情况下,您需要通过管道、文件、套接字等进行进程间通信......
多处理
您可以通过使用 php.ini 创建新进程(也包含新线程)来实现并行计算。如果您的线程不需要太多的通信或同步,这是您的选择,因为进程是隔离的,不会干扰彼此的工作。即使一个崩溃,这与其他人无关。如果您确实需要大量通信,您应该继续阅读“多线程”或 - 遗憾的是 - 考虑使用另一种编程语言,因为进程间通信和同步引入了很多复杂性。
在 php 中,您有两种创建新进程的方法:
让操作系统为您完成:您可以告诉您的操作系统创建一个新进程并在其中运行一个新的(或相同的)php 脚本。
对于linux,您可以使用以下内容或考虑Darryl Hein 的回答:
$cmd = 'nice php script.php 2>&1 & echo $!'; pclose(popen($cmd, 'r'));
对于Windows,你可以使用这个:
$cmd = 'start "processname" /MIN /belownormal cmd /c "script.php 2>&1"'; pclose(popen($cmd, 'r'));
用 fork 自己做:php 还提供了通过函数pcntl_fork()使用 fork 的可能性。可以在此处找到有关如何执行此操作的好教程,但我强烈建议不要使用它,因为fork 是危害人类的罪行,尤其是针对 oop 的罪行。
多线程
使用多线程,您的所有线程都共享它们的资源,因此您可以轻松地在它们之间进行通信和同步,而无需大量开销。另一方面,您必须知道自己在做什么,因为竞争条件和死锁很容易产生但很难调试。
标准 php 不提供任何多线程,但实际上有一个(实验性)扩展 - pthreads。它的 api 文档甚至进入了php.net。有了它,您可以像在真正的编程语言中一样做一些事情:-),如下所示:
class MyThread extends Thread {
public function run(){
//do something time consuming
}
}
$t = new MyThread();
if($t->start()){
while($t->isRunning()){
echo ".";
usleep(100);
}
$t->join();
}
对于linux ,stackoverflow 上有一个安装指南。
对于Windows,现在有一个:
- 首先你需要 php 的线程安全版本。
- 您需要 pthreads 及其 php 扩展的预编译版本。他们可以在这里下载。确保您下载的版本与您的 php 版本兼容。
- 将 php_pthreads.dll(从您刚刚下载的 zip 文件)复制到您的 php 扩展文件夹 ([phpDirectory]/ext)。
- 将 pthreadVC2.dll 复制到 [phpDirectory](根文件夹 - 而不是扩展文件夹)。
编辑 [phpDirectory]/php.ini 并插入以下行
extension=php_pthreads.dll
使用上面的脚本进行测试,并在评论所在的位置进行一些睡眠或其他操作。
现在是大问题:虽然这确实有效,但php 最初并不是为多线程而设计的。存在 php 的线程安全版本,从 v5.4 开始,它似乎几乎没有错误,但在 php 手册中仍然不鼓励在多线程环境中使用 php (但也许他们只是没有更新他们的手册这个,但是)。一个更大的问题可能是很多常见的扩展都不是线程安全的。所以你可能会得到这个 php 扩展的线程,但是你所依赖的函数仍然不是线程安全的,所以你可能会在你自己没有编写的代码中遇到竞争条件、死锁等等......
您可以使用pcntl_fork()来实现类似于线程的功能。从技术上讲它是独立的进程,因此两者之间的通信并不像线程那么简单,我相信如果 apache 调用 PHP 将无法正常工作。
如果有人在乎,我已经恢复了php_threading(与线程不同,但相似),我实际上已经将它工作到了(有点)很好的地步!
pcntl_fork()
是您正在搜索的内容,但它的进程分叉不是线程。所以你会有数据交换的问题。要解决它们,您可以使用 phps 信号量函数 ( http://www.php.net/manual/de/ref.sem.php ) 消息队列一开始可能比共享内存段更容易一些。
无论如何,我在我正在开发的 Web 框架中使用的一种策略,它并行加载网页的资源密集型块(可能带有外部请求):我正在做一个工作队列以知道我在等待什么数据,然后我分叉关闭每个进程的工作。完成后,他们将数据存储在父进程可以访问的唯一键下的 apc 缓存中。一旦每个数据都在那里,它就会继续。我正在使用简单usleep()
的等待,因为在 apache 中无法进行进程间通信(孩子将失去与父母的连接并成为僵尸......)。所以这让我想到了最后一件事:自我杀死每个孩子很重要!还有一些类可以分叉进程但保留数据,我没有检查它们,但 zend 框架有一个,它们通常执行缓慢但可靠的代码。你可以在这里找到它:
http://zendframework.com/manual/1.9/en/zendx.console.process.unix.overview.html
我认为他们使用 shm 段!最后但并非最不重要的是这个zend网站上有一个错误,这个例子中的小错误。
while ($process1->isRunning() && $process2->isRunning()) {
sleep(1);
}
should of course be:
while ($process1->isRunning() || $process2->isRunning()) {
sleep(1);
}
在https://github.com/krakjoe/pthreads上有一个基于 PThreads 正在积极开发的线程扩展,看起来很有希望
只是一个更新,似乎 PHP 人员正在支持线程并且它现在可用。
这是它的链接:http: //php.net/manual/en/book.pthreads.php
我有一个 PHP 线程类,它已经在生产环境中完美运行了两年多。
编辑:现在可以作为作曲家库和我的 MVC 框架 Hazaar MVC 的一部分使用。
我知道这是一个老问题,但你可以看看http://phpthreadlib.sourceforge.net/
双向通信,支持 Win32,无需扩展。
听说过appserver
techdivision吗?
它是用 php 编写的,用作管理高流量 php 应用程序的多线程的应用程序服务器。仍处于测试阶段,但很有前途。
有一个相当晦涩且很快就会被弃用的功能,称为ticks。我唯一用过的就是允许脚本捕获 SIGKILL (Ctrl+C) 并优雅地关闭。