如何在PHP应用程序中使用多线程

是否有一种可行的方法在PHP中实现多线程模型,无论是真正的,还是只是模拟它。以前曾有人建议,可以强制操作系统加载PHP可执行文件的另一个实例,并同时处理其他进程。

这样做的问题是,当PHP代码完成执行时,PHP实例仍然保留在内存中,因为没有办法从PHP内部杀死它。所以如果你正在模拟几个线程,你可以想象会发生什么。所以我仍然在寻找一种方法,多线程可以在PHP中有效地完成或模拟。什么好主意吗?

585180 次浏览

虽然不能使用线程,但在php中确实有某种程度的进程控制。这里有用的两个函数集是:

过程控制函数 http://www.php.net/manual/en/ref.pcntl.php < / p > < p > POSIX功能 http://www.php.net/manual/en/ref.posix.php < / p >

你可以用pcntl_fork来分叉你的进程——返回子进程的PID。然后你可以使用posix_kill来处理这个PID。

也就是说,如果你终止了父进程,就应该向子进程发送一个信号,告诉它终止。如果php本身没有识别这一点,你可以注册一个函数来管理它,并使用pcntl_signal做一个干净的退出。

您可以使用exec()来运行命令行脚本(例如命令行php),如果您将输出管道到一个文件,那么您的脚本将不会等待命令完成。

我不太记得php CLI的语法,但你会想要这样的东西:

exec("/path/to/php -f '/path/to/file.php' | '/path/to/output.txt'");

我认为相当多的共享托管服务器出于安全原因默认禁用了exec(),但可能值得一试。

常规PHP中没有线程,但是通过使用HTTP请求作为异步调用,可以实现并发编程。

将curl的超时设置设置为1,并为您希望相互关联的进程使用相同的session_id,您可以与会话变量通信,如下面的示例所示。使用这种方法,您甚至可以关闭浏览器,而并发进程仍然存在于服务器上。

不要忘记像这样验证正确的会话ID:

http://localhost/test/verifysession.php?sessionid= [正确id]

startprocess.php

$request = "http://localhost/test/process1.php?sessionid=".$_REQUEST["PHPSESSID"];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_exec($ch);
curl_close($ch);
echo $_REQUEST["PHPSESSID"];

process1.php

set_time_limit(0);


if ($_REQUEST["sessionid"])
session_id($_REQUEST["sessionid"]);


function checkclose()
{
global $_SESSION;
if ($_SESSION["closesession"])
{
unset($_SESSION["closesession"]);
die();
}
}


while(!$close)
{
session_start();
$_SESSION["test"] = rand();
checkclose();
session_write_close();
sleep(5);
}

verifysession.php

if ($_REQUEST["sessionid"])
session_id($_REQUEST["sessionid"]);


session_start();
var_dump($_SESSION);

closeprocess.php

if ($_REQUEST["sessionid"])
session_id($_REQUEST["sessionid"]);


session_start();
$_SESSION["closesession"] = true;
var_dump($_SESSION);

您可以模拟线程。PHP可以通过popen(或proc_open)运行后台进程。这些进程可以通过stdin和stdout进行通信。当然,这些进程本身也可以是一个php程序。这可能是你能找到的最接近的了。

为什么不使用popen呢?

for ($i=0; $i<10; $i++) {
// open ten processes
for ($j = 0; $j < 10; $j++) {
$pipe[$j] = popen('script2.php', 'w');
}


// wait for them to finish
for ($j = 0; $j < 10; ++$j) {
pclose($pipe[$j]);
}
}

取决于你要做什么,你也可以使用curl_multi来实现它。

您可以选择:

  1. multi_curl
  2. 可以使用系统命令来实现相同的功能
  3. 理想的情况是,用C语言创建一个线程函数,然后用PHP编译/配置。这个函数是PHP的函数。

那么pcntl_fork呢?

检查我们的手册页的例子:PHP pcntl_fork

<?php


$pid = pcntl_fork();
if ($pid == -1) {
die('could not fork');
} else if ($pid) {
// we are the parent
pcntl_wait($status); //Protect against Zombie children
} else {
// we are the child
}


?>

如果打开了安全模式pcntl_fork将不能在web服务器环境中工作。在这种情况下,它只能在CLI版本的PHP中工作。

我知道这是一个老问题,但对于搜索的人来说,有一个用C编写的PECL扩展,它现在提供了PHP多线程功能,它位于https://github.com/krakjoe/pthreads

多线程在php中是可能的

是的,你可以用pthreads在PHP中实现多线程

PHP文档:

pthreads是一个面向对象的API,它提供了PHP多线程所需的所有工具。PHP应用程序可以创建、读取、写入、执行和同步线程、工作者和线程对象。

< p > 警告: pthreads扩展不能在web服务器环境中使用。因此,PHP中的线程应该只用于基于cli的应用程序

简单的测试

#!/usr/bin/php
<?php
class AsyncOperation extends Thread {


public function __construct($arg) {
$this->arg = $arg;
}


public function run() {
if ($this->arg) {
$sleep = mt_rand(1, 10);
printf('%s: %s  -start -sleeps %d' . "\n", date("g:i:sa"), $this->arg, $sleep);
sleep($sleep);
printf('%s: %s  -finish' . "\n", date("g:i:sa"), $this->arg);
}
}
}


// Create a array
$stack = array();


//Initiate Multiple Thread
foreach ( range("A", "D") as $i ) {
$stack[] = new AsyncOperation($i);
}


// Start The Threads
foreach ( $stack as $t ) {
$t->start();
}


?>

第一次运行

12:00:06pm:     A  -start -sleeps 5
12:00:06pm:     B  -start -sleeps 3
12:00:06pm:     C  -start -sleeps 10
12:00:06pm:     D  -start -sleeps 2
12:00:08pm:     D  -finish
12:00:09pm:     B  -finish
12:00:11pm:     A  -finish
12:00:16pm:     C  -finish

第二次运行

12:01:36pm:     A  -start -sleeps 6
12:01:36pm:     B  -start -sleeps 1
12:01:36pm:     C  -start -sleeps 2
12:01:36pm:     D  -start -sleeps 1
12:01:37pm:     B  -finish
12:01:37pm:     D  -finish
12:01:38pm:     C  -finish
12:01:42pm:     A  -finish

现实世界中的例子

error_reporting(E_ALL);
class AsyncWebRequest extends Thread {
public $url;
public $data;


public function __construct($url) {
$this->url = $url;
}


public function run() {
if (($url = $this->url)) {
/*
* If a large amount of data is being requested, you might want to
* fsockopen and read using usleep in between reads
*/
$this->data = file_get_contents($url);
} else
printf("Thread #%lu was not provided a URL\n", $this->getThreadId());
}
}


$t = microtime(true);
$g = new AsyncWebRequest(sprintf("http://www.google.com/?q=%s", rand() * 10));
/* starting synchronization */
if ($g->start()) {
printf("Request took %f seconds to start ", microtime(true) - $t);
while ( $g->isRunning() ) {
echo ".";
usleep(100);
}
if ($g->join()) {
printf(" and %f seconds to finish receiving %d bytes\n", microtime(true) - $t, strlen($g->data));
} else
printf(" and %f seconds to finish, request failed\n", microtime(true) - $t);
}

pthreads PECL扩展使使用线程成为可能

http://www.php.net/manual/en/book.pthreads.php < a href = " http://www.php.net/manual/en/book.pthreads.php " > < / >

多线程意味着同时执行多个任务或进程,我们可以通过使用以下代码在php中实现这一点,尽管在php中没有直接的方法来实现多线程,但我们可以通过以下方法实现几乎相同的结果。

chdir(dirname(__FILE__));  //if you want to run this file as cron job
for ($i = 0; $i < 2; $i += 1){
exec("php test_1.php $i > test.txt &");
//this will execute test_1.php and will leave this process executing in the background and will go


//to next iteration of the loop immediately without waiting the completion of the script in the


//test_1.php , $i  is passed as argument .

Test_1.php

$conn=mysql_connect($host,$user,$pass);
$db=mysql_select_db($db);
$i = $argv[1];  //this is the argument passed from index.php file
for($j = 0;$j<5000; $j ++)
{
mysql_query("insert  into  test   set


id='$i',


comment='test',


datetime=NOW() ");


}

这将同时执行test_1.php两次,两个进程将同时在后台运行,因此通过这种方式,您可以在php中实现多线程。

这家伙做得很好php多线程

在我写这篇评论的时候,我对PHP线程还不了解。我自己在这里寻找答案,但是一个解决方案是,从web服务器接收请求的PHP程序将整个答案公式委托给一个控制台应用程序,该应用程序将其输出,请求的答案,存储到一个二进制文件中,启动控制台应用程序的PHP程序将该二进制文件逐字节返回,作为接收到的请求的答案。控制台应用程序可以用运行在服务器上的任何编程语言编写,包括那些具有适当线程支持的语言,包括使用OpenMP的c++程序。

一个不可靠的,肮脏的技巧是使用PHP来执行控制台应用程序,"uname",

uname -a

并将控制台命令的输出输出到HTML输出中,以找出服务器软件的确切版本。然后将完全相同版本的软件安装到VirtualBox实例中,编译/组装任何完全独立的二进制文件,最好是静态的,然后将它们上传到服务器。从那时起,PHP应用程序就可以在具有适当多线程的控制台应用程序的角色中使用这些二进制文件。当服务器管理员没有在服务器上安装所有需要的编程语言实现时,这是一种肮脏、不可靠的解决方案。需要注意的是,在PHP应用程序接收到的每个请求中,控制台应用程序都会终止/exit/get_killed。

至于托管服务管理员对这种服务器使用模式的看法,我想这可以归结为文化。在北欧,如果允许执行控制台命令,允许上传非恶意软件文件,并且服务提供商有权在几分钟甚至30秒后终止任何服务器进程,那么主机服务管理员就没有理由提出适当的投诉。美国和西欧的情况/文化非常不同,我相信美国和/或西欧的托管服务提供商很有可能会这样做 拒绝为使用上述技巧的托管服务客户端提供服务。这只是我的猜测,根据我在美国托管服务方面的个人经验,以及我从其他人那里听到的关于西欧托管服务的信息。在撰写我目前的评论(2018_09_01)时,我对南欧托管服务提供商、南欧网络管理员的文化规范一无所知

如果您正在使用Linux服务器,则可以使用

exec("nohup $php_path path/script.php > /dev/null 2>/dev/null &")

如果你需要传递一些参数

exec("nohup $php_path path/script.php $args > /dev/null 2>/dev/null &")

在script.php

$args = $argv[1];

或使用Symfony https://symfony.com/doc/current/components/process.html < / p >

$process = Process::fromShellCommandline("php ".base_path('script.php'));
$process->setTimeout(0);
$process->disableOutput();
$process->start();

我知道这是一个老问题,但这无疑对许多人有用:PHPThreads

代码示例:

function threadproc($thread, $param) {
 

echo "\tI'm a PHPThread.  In this example, I was given only one parameter: \"". print_r($param, true) ."\" to work with, but I can accept as many as you'd like!\n";
 

for ($i = 0; $i < 10; $i++) {
usleep(1000000);
echo "\tPHPThread working, very busy...\n";
}
 

return "I'm a return value!";
}
 



$thread_id = phpthread_create($thread, array(), "threadproc", null, array("123456"));
 

echo "I'm the main thread doing very important work!\n";
 

for ($n = 0; $n < 5; $n++) {
usleep(1000000);
echo "Main thread...working!\n";
}
 

echo "\nMain thread done working.  Waiting on our PHPThread...\n";
 

phpthread_join($thread_id, $retval);
 

echo "\n\nOur PHPThread returned: " . print_r($retval, true) . "!\n";

需要PHP扩展:

  • posix
  • pcntl
  • 套接字

我已经在生产中使用这个库好几个月了。我花了很多精力让它看起来像在使用POSIX pthread。如果您熟悉pthread,那么您可以立即学习并非常有效地使用它。

在计算上,内部工作方式有很大不同,但实际上,功能几乎是相同的,包括语义和语法。

我用它编写了一个非常高效的WebSocket服务器,支持高吞吐率。对不起,我跑题了。我只是很兴奋,我终于把它发布了,我想看看它会帮助谁!

popen()/proc_open()即使在Windows中也可以并行工作。

最常见的陷阱是“fread/stream_get_contents"没有while循环。一旦你试图从正在运行的进程中fread(),它将阻塞在它之后运行的进程的输出(因为fread()等待至少一个字节到达)

添加stream_select()。最接近的类比是带超时的"foreach;但对于streams",你传递几个数组来读写,每次调用stream_select()都会选择一个或多个流。函数通过引用更新原始数组,所以不要忘记在下次调用之前将其恢复到所有流。函数给它们一些时间来读或写。如果没有内容控制返回,允许我们重试循环。

// sleep.php
set_error_handler(function ($severity, $error, $file, $line) {
throw new ErrorException($error, -1, $severity, $file, $line);
});


$sleep = $argv[ 1 ];


sleep($sleep);


echo $sleep . PHP_EOL;


exit(0);
// run.php
<?php


$procs = [];
$pipes = [];


$cmd = 'php %cd%/sleep.php';


$desc = [
0 => [ 'pipe', 'r' ],
1 => [ 'pipe', 'w' ],
2 => [ 'pipe', 'a' ],
];


for ( $i = 0; $i < 10; $i++ ) {
$iCmd = $cmd . ' ' . ( 10 - $i ); // add SLEEP argument to each command 10, 9, ... etc.


$proc = proc_open($iCmd, $desc, $pipes[ $i ], __DIR__);


$procs[ $i ] = $proc;
}


$stdins = array_column($pipes, 0);
$stdouts = array_column($pipes, 1);
$stderrs = array_column($pipes, 2);


while ( $procs ) {
foreach ( $procs as $i => $proc ) {
// @gzhegow > [OR] you can output while script is running (if child never finishes)
$read = [ $stdins[ $i ] ];
$write = [ $stdouts[ $i ], $stderrs[ $i ] ];
$except = [];
if (stream_select($read, $write, $except, $seconds = 0, $microseconds = 1000)) {
foreach ( $write as $stream ) {
echo stream_get_contents($stream);
}
}


$status = proc_get_status($proc);


if (false === $status[ 'running' ]) {
$status = proc_close($proc);
unset($procs[ $i ]);


echo 'STATUS: ' . $status . PHP_EOL;
}


// @gzhegow > [OR] you can output once command finishes
// $status = proc_get_status($proc);
//
// if (false === $status[ 'running' ]) {
//     if ($content = stream_get_contents($stderrs[ $i ])) {
//         echo '[ERROR]' . $content . PHP_EOL;
//     }
//
//     echo stream_get_contents($stdouts[ $i ]) . PHP_EOL;
//
//     $status = proc_close($proc);
//     unset($procs[ $i ]);
//
//     echo 'STATUS: ' . $status . PHP_EOL;
// }
}


usleep(1); // give your computer one tick to decide what thread should be used
}


// ensure you receive 1,2,3... but you've just run it 10,9,8...


exit(0);