是否有一种可行的方法在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);

其他回答

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

过程控制功能 http://www.php.net/manual/en/ref.pcntl.php

POSIX函数 http://www.php.net/manual/en/ref.posix.php

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

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

多线程意味着同时执行多个任务或进程,我们可以通过使用以下代码在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多线程方面做得很好

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.

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);

常规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);