PHP有线程吗?


130

我发现了这个称为线程的PECL包,但是还没有发布。PHP网站上没有任何内容。


有人知道如果pcntl_fork()从Apache调用this()是否可以工作?
乔什·K

这太老了,但是我有一个答案,实际上是在php中提供线程(请参阅下面的链接)。
亚历克峡谷

他们建议不要从服务器环境中调用fork。我不怪他们。但是,pcntl_fork似乎确实是线程化PHP的最佳解决方案。
just_wes 2011年

是的,您不需要派生一个apache2 php进程。
andho 2012年

2
使用pthread就像魅力一样工作
爸爸

Answers:


40

我知道没有可用的东西。第二个最好的办法是简单地使一个脚本通过CLI执行另一个脚本,但这有点基本。根据您尝试执行的操作以及操作的复杂程度,这可能是,也可能不是。


1
我也这么想。我看到一堆老帖子说不,在php.net上什么也没有,所以这是我的想法。感谢您确认。
Thomas Owens

2
是的,那个PECL包有点像在逗弄我也碰到过它,但是什么也没碰到。
Wilco

180

从PHP手册中的pthreads扩展中:

pthreads是一个面向对象的API,允许在PHP中进行用户级多线程处理。它包括创建针对Web或控制台的多线程应用程序所需的所有工具。PHP应用程序可以创建,读取,写入,执行并与线程,辅助线程和可堆栈对象同步。

听起来令人难以置信,这是完全正确的。今天,PHP可以为希望尝试的人提供多线程。

PHP 4的第一版发布于2000年5月22日,它带有线程安全体系结构-一种在多线程SAPI(Server API)环境中的单独线程中执行其解释程序的多个实例的方法。在过去的13年中,此体系结构的设计得到了维护和改进:从那时起,它已在世界上最大的网站上投入生产。

对于PHP团队来说,在用户领域内进行线程处理从来都不是一个问题,并且今天仍然如此。您应该了解,在PHP开展业务的世界中,已经有一种定义的扩展方法-添加硬件。PHP已经存在了许多年,硬件变得越来越便宜,因此这对于PHP团队来说越来越少了。虽然它越来越便宜,但它的功能也越来越强大。如今,我们的手机和平板电脑具有双核和四核架构以及大量的RAM,我们的台式机和服务器通常具有8或16核,16和32 GB的RAM,尽管我们可能并不总是能够拥有两个在预算之内并且拥有两个台式机对我们大多数人来说很少有用。

另外,PHP是为非程序员编写的,它是许多业余爱好者的母语。之所以很容易采用PHP,是因为它是一种易于学习和编写的语言。今天PHP之所以如此可靠的原因是,由于PHP的设计工作量很大,并且PHP小组做出了每个决定。这些年来,它的可靠性和绝对的优势使其备受关注。它的竞争对手已经陷入时间或压力。

对于大多数人来说,多线程编程并不容易,即使使用最一致,最可靠的API,也要考虑不同的事情,并且会产生许多误解。PHP小组不希望用户登陆多线程成为核心功能,因此从来没有给予过认真的关注-的确如此。PHP对每个人来说都不应该很复杂。

考虑到所有因素,允许PHP利用其生产准备就绪和经过测试的功能仍然有好处,这可以使我们充分利用现有的功能,而添加更多的功能并不总是一种选择,并且在很多方面从未真正需要任何任务。

对于希望探索它的人来说,pthreads实现了一个API,该API确实允许用户对PHP应用程序进行多线程处理。它的API尚在开发中,并指定了beta级别的稳定性和完整性。

众所周知,PHP使用的某些库不是线程安全的,程序员应该清楚pthreads不能更改它,也不会尝试尝试。但是,可以使用任何线程安全的库,就像在解释程序的任何其他线程安全设置中一样。

pthreads利用Posix线程(即使在Windows中也是如此),程序员创建的是真正的执行线程,但是对于那些有用的线程,他们必须了解PHP-能够执行用户代码,共享变量并允许有用的通信方式(同步)。因此,每个线程都是使用解释器的实例创建的,但是根据设计,它的解释器与解释器的所有其他实例是隔离的-就像多线程Server API环境一样。pthreads尝试以理智而安全的方式弥合差距。p线程的程序员并没有解决C语言中的线程程序员的许多问题,根据设计,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时,其refcount为0,从而使引擎能够在操作完成后释放副本,因为不存在对其的其他引用。

(3)preg_match的最后一个参数引用数据存储,获得读取锁,将(1)中设置的数据复制到zval,引用计数再次为0。释放该锁,对preg_match的调用在数据副本,它本身就是原始数据的副本。

要知道的事情:

  • 对象存储的哈希表在其中存储数据(线程安全)是
    基于Zend随PHP附带的TsHashTable的。

  • 对象存储区具有读写锁,为TsHashTable提供了一个附加的访问锁,以便在需要时(并且确实如此,var_dump / print_r,当PHP引擎希望引用属性时直接访问属性)pthread可以操纵TsHashTable在定义的API之外。

  • 仅在复制操作发生时才持有锁,复制完成后才以合理的顺序释放锁。

这表示:

  • 发生写操作时,不仅会保留读和写锁,还会保留其他访问锁。该表本身已被锁定,其他上下文无法锁定,读取,写入或影响该表。

  • 发生读取时,不仅会保留读取锁,还会保留附加的访问锁,从而再次锁定表。

没有两个上下文可以物理地或并行地从对象存储中访问相同的数据,但是在任何带有引用的上下文中进行的写操作都会影响在带有引用的任何上下文中读取的数据。

这是没有任何共享的体系结构,并且存在的唯一途径是共存。那些精明的人会看到,这里有很多复制工作,他们会怀疑这是否是一件好事。在动态运行时中进行了大量复制,这就是动态语言的动态。pthreads是在对象级别实现的,因为可以对一个对象获得良好的控制,但是方法-程序员执行的代码-具有另一个上下文,没有锁定和复制-本地方法范围。在使用pthreads对象的情况下,对象范围应被视为在上下文之间共享数据的一种方式,这就是其目的。考虑到这一点,您可以采用一些技术来避免锁定对象存储,除非有必要,

可用于PHP的大多数库和扩展都是围绕第三方的精简包装,在某种程度上PHP的核心功能是一回事。pthreads并不是Posix Thread的薄薄包装。它是基于Posix Threads的线程API。在PHP中实现用户不理解或无法使用的线程是没有意义的。没有任何理由不了解互斥量是或不知道互斥量的人在技能和资源上都无法利用他们所拥有的一切。一个对象的功能类似于一个对象,但是只要两个上下文相互冲突,pthread就可以提供稳定性和安全性。

在Java中工作过的任何人都将看到pthreads对象与Java中的线程之间的相似之处,这些相同的人无疑会看到一个称为ConcurrentModificationException的错误-因为如果两个线程写入相同的物理数据,这听起来会由Java运行时引发错误。同时。我理解为什么会存在它,但令我感到困惑的是,资源如此之便宜,再加上运行时能够在用户唯一能够实现安全的确切时间检测并发,因此它选择了在运行时引发可能致命的错误,而不是管理执行和对数据的访问。

我相信,pthread不会发出这种愚蠢的错误,编写该API是为了使线程尽可能稳定和兼容。

多线程并不像使用新数据库那样,应密切注意手册和pthread附带的示例中的每个单词。

最后,从PHP手册中:

pthreads曾经是并且现在是一个效果很好的实验。它的任何限制或功能可能随时更改;这就是实验的本质。它的局限性-通常是由实施施加的-有充分的理由;pthreads的目的是为任何级别的PHP中的多任务提供一个有用的解决方案。在执行pthread的环境中,为了提供稳定的环境,需要一些限制和限制。


14
废话。
2014年

7
@GeoC。我什至不知道您的意思是什么,这只是一堆胡言乱语,并且您不提供任何理由(无论是否合乎逻辑),为什么您不同意任何论点(我在帖子中看不到任何论点) 。
Jimbo 2014年

13
@Tudor我不认为您真的知道您在说什么,所以我很乐意忽略您。
Joe Watkins 2014年

4
@Tudor-许多科学家没有“明白要点”,这是他们分支中的新事物或有用的事物。历史表明,像你这样的人常常是错的,这是事实。就像事实一样,您在此处编写的所有内容都缺少粪便。我强烈建议,从积极的角度出发,不要参加您一无所知的话题(这是其中之一)。
NB

12
有趣的事情。乔·沃特金斯(Joe Watkins)是pthreads的作者,但都铎(Tudor)仍然试图证明他错了。
赫里斯托·瓦尔卡诺夫

48

这是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/


我有一个小问题,您如何检查后台进程的状态?您能启发我

25

简而言之:是的,php中有多线程处理,但是您应该改用多处理。

背景信息:线程与进程

关于线程和进程的区别总是有一些困惑,因此我将简要介绍两者:

  • 线程是CPU将处理命令序列。它组成的唯一数据是程序计数器。每个CPU内核一次只能处理一个线程,但可以通过调度在不同线程的执行之间进行切换。
  • 过程是一组共享资源。这意味着它由一部分内存,变量,对象实例,文件句柄,互斥对象,数据库连接等组成。每个进程还包含一个或多个线程。同一进程的所有线程共享其资源,因此您可以在另一个线程中创建的一个线程中使用变量。如果这些线程是两个不同进程的一部分,那么它们将无法直接访问彼此的资源。在这种情况下,您需要通过管道,文件,套接字等进行进程间通信

多处理

您可以通过使用php创建新进程(也包含一个新线程)来实现并行计算。如果您的线程不需要太多的通信或同步,这是您的选择,因为进程是隔离的,并且不会干扰彼此的工作。即使其中一个崩溃,也与其他人无关。如果确实需要大量通信,则应该阅读“多线程”,或者-不幸的是-考虑使用另一种编程语言,因为进程间的通信和同步会带来很多麻烦。

在php中,您可以通过两种方式创建新进程:

让OS为您完成操作:您可以告诉操作系统创建一个新进程并在其中运行新的(或相同的)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'));

自己用叉子来做:php还提供了通过pcntl_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的线程安全版本。
  • 您需要两个pthread及其php扩展名的预编译版本。可以在这里下载。确保您下载的版本与您的php版本兼容。
  • 将php_pthreads.dll(从刚刚下载的zip文件中)复制到php扩展文件夹([phpDirectory] ​​/ ext)中。
  • 将pthreadVC2.dll复制到[phpDirectory](根文件夹-而不是扩展文件夹)。
  • 编辑[phpDirectory] ​​/php.ini并插入以下行

    extension=php_pthreads.dll
  • 在上面的脚本中测试一下,然后睡一会儿或在注释所在的地方进行测试。

而现在的大BUT:虽然这还真管用,PHP最初不是为多线程做。存在一个php的线程安全版本,从v5.4开始,它似乎几乎没有错误,但是php手册中仍然不鼓励在多线程环境中使用php(但也许他们只是没有更新手册)这个)。更大的问题可能是许多常见的扩展不是线程安全的。因此,您可能会获得具有此php扩展名的线程,但是您所依赖的功能仍然不是线程安全的,因此您可能会遇到竞争条件,死锁等情况,而这些原因并非是您自己编写的代码...


5
这是完全错误的,您所引用的文章是2008年的。如果PHP的核心不是线程安全的,那么它将没有线程化的SAPI模块。
Joe Watkins 2013年

1
@Joe:好吧,我在核心中更改了它是线程安全的,但很多扩展不是。
弗朗索瓦·布尔乔亚

1
很多?我认为您会发现它很少,找到了文档,但未能正确阅读:注意:带有*的标记不是线程安全的库,因此不应在PHP中将它作为服务器模块使用。线程的Windows Web服务器(IIS,Netscape)。但这在Unix环境中并不重要。
Joe Watkins 2013年

6
PHP是非常线程安全的,并且已经存在很多年了,其中一些外部库和几个捆绑库不是,但是它有据可查,并且无论如何还是很明显的。pthreads创建的线程与zend在多线程sapi中创建的线程一样安全,我知道这一点,因为我独自编写了pthreads。它使用了PHP公开的所有可用API,就像服务器API一样,我并不是说它是完全稳定的,但是您所绘制的图片完全是错误的,而且通知很差。
Joe Watkins 2013年

@Joe:当手册说这对于Unix环境无所谓时,他们指的是在Unix系统上apache使用多个进程,而在Windows上它使用线程。因此,基本上他们说的是“如果您仍然不使用线程,则不必担心非线程安全的扩展”。当我们将线程与pthreads一起使用时,它当然在Unix环境中也很重要。
弗朗索瓦·布尔乔亚

17

您可以使用pcntl_fork()实现类似于线程的功能。从技术上讲,它是独立的进程,因此两者之间的通信不是那么简单,并且我相信,如果PHP由apache调用,它将无法正常工作。


4
我已成功使用pcntl_fork并行化了一个相当大的数据导入任务。效果很好,我花了大约一个小时的时间。有一些学习曲线,但是一旦您了解发生了什么,就很简单了。
Frank Farmer

弗兰克,那是CLI php还是Apache PHP?
Artem Russakovskii 09年

@Artem:我也想知道。
乔什·K

5
@Frank Farmer正在取笑我们...就像PECL包一样。
罗杰

1
我在CLI上使用pcntl_fork。我从来没有尝试过它。听起来很冒险。即使在CLI上,也存在一些意外的问题。我似乎遇到了一个问题:如果一个孩子关闭了数据库句柄(因为它完成了工作),那么它也关闭了同级的连接。由于孩子是父母的复制品,因此请为奇怪做准备。此后,我重新设计了代码,以通过exec()产生新的,完全独立的进程-这样更干净。
Frank Farmer


7

pcntl_fork()是您要搜索的内容,但是它的过程没有线程。因此您将遇到数据交换问题。要解决它们,您可以使用phps信号量函数(http://www.php.net/manual/de/ref.sem.php)开始时,消息队列可能比共享内存段要容易一些。

无论如何,我正在开发的Web框架中正在使用的一种策略是并行加载网页的资源密集型块(可能带有外部请求):我正在做一个工作队列,以了解我正在等待什么数据,然后分叉关闭每个过程的工作。完成后,它们将数据存储在父进程可以访问的唯一密钥下的apc缓存中。一旦每个数据都存在,它将继续。我使用简单的usleep()方法等待,因为在Apache中无法进行进程间通信(孩子们将失去与父母的联系并变成僵尸……)。因此,这使我想到了最后一件事:重要的是要杀死每个孩子!还有一些派生进程但保留数据的类,我没有检查它们,但zend framework有一个,它们通常执行缓慢但可靠的代码。你可以在这里找到它: 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);
}



5

我有一个PHP线程类,该类已经在生产环境中完美运行了两年以上。

编辑:现在可以作为作曲家库和我的MVC框架Hazaar MVC的一部分使用。

参见:https : //git.hazaarlabs.com/hazaar/hazaar-thread


例如,如果按照您的示例,例如file.php中的程序,该文件可以满足10k网站uri列表的存在,然后必须将结果保存为CSV文件...该文件写入为问题?
罗杰

子进程将以与Web服务器/父脚本相同的用户身份运行。因此,在编写文件时,您将具有与通常一样的权限注意事项。如果您在写入文件时遇到问题,请尝试写入/ tmp,然后在工作时从那里开始。
杰米·卡尔

1
由于重新设计,该链接现已失效,您可以在回溯机器上通过以下网址获取它:web.archive.org/web/20130922043615/http
Tony

现在添加到我的MVC框架中。参见:git.hazaarlabs.com/hazaar/hazaar-thread
Jamie Carl


1

听说过appserverTechdivision吗?

它是用php编写的,并且用作管理高流量php应用程序的多线程的应用服务器。仍处于测试阶段,但非常吸引人。


-3

有一个相当模糊的功能,即将被弃用,称为ticks。我使用过的唯一东西是允许脚本捕获SIGKILL(Ctrl + C)并正常关闭。


3
刻度不会并行执行。本质上,每条语句之后,您的tick函数都会运行。当您的报价功能运行时,主代码未运行。
Frank Farmer

1
勾选仅适用于signal()处理函数。
尼克
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.