是否有一种可行的方法在PHP中实现多线程模型,无论是真正的,还是只是模拟它。以前曾有人建议,可以强制操作系统加载PHP可执行文件的另一个实例,并同时处理其他进程。
这样做的问题是,当PHP代码完成执行时,PHP实例仍然保留在内存中,因为没有办法从PHP内部杀死它。所以如果你正在模拟几个线程,你可以想象会发生什么。所以我仍然在寻找一种方法,多线程可以在PHP中有效地完成或模拟。什么好主意吗?
是否有一种可行的方法在PHP中实现多线程模型,无论是真正的,还是只是模拟它。以前曾有人建议,可以强制操作系统加载PHP可执行文件的另一个实例,并同时处理其他进程。
这样做的问题是,当PHP代码完成执行时,PHP实例仍然保留在内存中,因为没有办法从PHP内部杀死它。所以如果你正在模拟几个线程,你可以想象会发生什么。所以我仍然在寻找一种方法,多线程可以在PHP中有效地完成或模拟。什么好主意吗?
当前回答
popen()/proc_open()即使在Windows中也可以并行工作。
最常见的陷阱是“fread/stream_get_contents”没有while循环。一旦你试图从正在运行的进程中fread(),它将阻塞在它之后运行的进程的输出(因为fread()等待至少一个字节到达)
添加stream_select()。最接近的类比是“foreach with timeout but for 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);
其他回答
As of the writing of my current comment, I don't know about the PHP threads. I came to look for the answer here myself, but one workaround is that the PHP program that receives the request from the web server delegates the whole answer formulation to a console application that stores its output, the answer to the request, to a binary file and the PHP program that launched the console application returns that binary file byte-by-byte as the answer to the received request. The console application can be written in any programming language that runs on the server, including those that have proper threading support, including C++ programs that use OpenMP.
一个不可靠的,肮脏的技巧是使用PHP来执行控制台应用程序,"uname",
uname -a
and print the output of that console command to the HTML output to find out the exact version of the server software. Then install the exact same version of the software to a VirtualBox instance, compile/assemble whatever fully self-contained, preferably static, binaries that one wants and then upload those to the server. From that point onwards the PHP application can use those binaries in the role of the console application that has proper multi-threading. It's a dirty, unreliable, workaround to a situation, when the server administrator has not installed all needed programming language implementations to the server. The thing to watch out for is that at every request that the PHP application receives the console application(s) terminates/exit/get_killed.
As to what the hosting service administrators think of such server usage patterns, I guess it boils down to culture. In Northern Europe the service provider HAS TO DELIVER WHAT WAS ADVERTISED and if execution of console commands was allowed and uploading of non-malware files was allowed and the service provider has a right to kill any server process after a few minutes or even after 30 seconds, then the hosting service administrators lack any arguments for forming a proper complaint. In United States and Western Europe the situation/culture is very different and I believe that there's a great chance that in U.S. and/or Western Europe the hosting service provider will refuse to serve hosting service clients that use the above described trick. That's just my guess, given my personal experience with U.S. hosting services and given what I have heard from others about Western European hosting services. As of the writing of my current comment(2018_09_01) I do not know anything about the cultural norms of the Southern-European hosting service providers, Southern-European network administrators.
如果您正在使用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
$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服务器,支持高吞吐率。对不起,我跑题了。我只是很兴奋,我终于把它发布了,我想看看它会帮助谁!
警告:这个扩展被认为是不可维护和死亡的。 警告:pthreads扩展不能在web服务器环境中使用。因此,PHP中的线程只局限于基于cli的应用程序。 警告:pthreads (v3)只能在PHP 7.2+中使用:这是因为ZTS模式在7.0和7.1中是不安全的。
https://www.php.net/manual/en/intro.pthreads.php
多线程在php中是可能的
是的,你可以用pthreads在PHP中实现多线程
来自PHP文档:
pthreads是一个面向对象的API,它提供了PHP多线程所需的所有工具。PHP应用程序可以创建、读取、写入、执行和同步线程、工作者和线程对象。 警告: 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);
}
为什么不用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]);
}
}