Как можно использовать многопоточность в приложениях PHP
Есть ли реалистичный способ реализации многопоточной модели в PHP, будь то по-настоящему или просто имитировать ее. Некоторое время назад было высказано предположение, что вы можете заставить операционную систему загружать другой экземпляр исполняемого файла PHP и обрабатывать другие одновременные процессы.
Проблема в том, что когда PHP-код завершил выполнение, экземпляр PHP остается в памяти, потому что нет способа уничтожить его изнутри PHP. Поэтому, если вы моделируете несколько потоков, вы можете представить, что произойдет. Поэтому я все еще ищу способ, которым многопоточность может быть эффективно реализована или смоделирована изнутри PHP. Есть идеи?
22 ответа
Многопоточность возможна в php
Да, вы можете сделать многопоточность в PHP с помощью pthreads
Из документации PHP:
pthreads - это объектно-ориентированный API, который предоставляет все инструменты, необходимые для многопоточности в PHP. Приложения PHP могут создавать, читать, писать, выполнять и синхронизировать с потоками, рабочими и потоковыми объектами.
Предупреждение. Расширение pthreads нельзя использовать в среде веб-сервера. Поэтому многопоточность в 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]);
}
}
Потоки не доступны в стандартном PHP, но параллельное программирование возможно при использовании HTTP-запросов в качестве асинхронных вызовов.
Если для параметра curl timeout установлено значение 1 и используется один и тот же session_id для процессов, которые вы хотите связать друг с другом, вы можете связываться с переменными сеанса, как в моем примере ниже. С помощью этого метода вы можете даже закрыть свой браузер, и параллельный процесс все еще существует на сервере.
Не забудьте проверить правильный идентификатор сессии, как это:
http://localhost/test/verifysession.php?sessionid=[the правильный идентификатор]
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. Здесь полезны два набора функций:
Функции управления процессом 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.
Использование потоков стало возможным благодаря расширению pthreads PECL
Я знаю, что это старый вопрос, но для людей, ищущих, есть расширение PECL, написанное на C, которое теперь дает возможность многопоточности PHP, оно находится здесь https://github.com/krakjoe/pthreads
Вы можете использовать exec() для запуска сценария командной строки (например, php командной строки), и если вы передадите вывод в файл, тогда ваш сценарий не будет ждать завершения команды.
Я не совсем помню синтаксис php CLI, но вы бы хотели что-то вроде:
exec("/path/to/php -f '/path/to/file.php' | '/path/to/output.txt'");
Я думаю, что для многих серверов общего хостинга exec() отключен по умолчанию из соображений безопасности, но, возможно, стоит попробовать.
Если вы используете сервер 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];
Или используйте Symfonyhttps://symfony.com/doc/current/components/process.html
$process = Process::fromShellCommandline("php ".base_path('script.php'));
$process->setTimeout(0);
$process->disableOutput();
$process->start();
Вы можете иметь возможность:
- multi_curl
- Можно использовать системную команду для того же
- Идеальный сценарий - создать функцию потоков на языке Си и скомпилировать / настроить на PHP. Теперь эта функция будет функцией PHP.
Как насчет pcntl_fork?
проверьте нашу страницу руководства для примеров: PHP pcntl_fork
Вы могли бы симулировать потоки. PHP может запускать фоновые процессы через popen (или proc_open). Эти процессы могут быть переданы через stdin и stdout. Конечно, эти процессы сами могут быть php-программами. Это, вероятно, так близко, как вы получите.
В зависимости от того, что вы пытаетесь сделать, вы также можете использовать curl_multi для этого.
pcntl_fork
не будет работать в среде веб-сервера, если включен безопасный режим. В этом случае он будет работать только в CLI-версии PHP.
Я знаю, что это старый вопрос, но он, несомненно, будет полезен многим: 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-потоков. Если вы хорошо разбираетесь в pthreads, вы можете воспользоваться этим и очень эффективно использовать его в кратчайшие сроки.
В вычислительном отношении внутренняя работа совершенно другая, но практически функциональность почти одинакова, включая семантику и синтаксис.
Я использовал его для написания чрезвычайно эффективного сервера WebSocket, поддерживающего высокую пропускную способность. Извините, я болтаю. Я просто взволнован тем, что наконец выпустил его, и я хочу посмотреть, кому он поможет!
Я знаю, что это старый, но вы можете посмотреть на http://phpthreadlib.sourceforge.net/
Он поддерживает двунаправленную связь между потоками, а также имеет встроенную защиту для уничтожения дочерних потоков (предотвращения сирот).
Не используйте pthreads.
https://www.php.net/manual/en/intro.pthreads.php
«Это расширение считается неподдерживаемым и мертвым. Вместо этого рассмотрите возможность использования параллельного интерфейса».
Для многопоточности HTTP с использованием GuzzleHttp (поскольку pthreads может не работать в веб-приложениях), см. Блог ниже - https://blog.madewithlove.be/post/concurrent-http-requests/
popen()/proc_open() работает параллельно даже в Windows.
Чаще всего подводным камнем является "fread/stream_get_contents" без цикла while. Как только вы попытаетесь выполнить fread() из запущенного процесса, он заблокирует вывод для процессов, которые запускаются после него (причина fread() ждет, пока не прибудет хотя бы один байт)
Добавьте stream_select(). Ближайшая аналогия - "foreach с тайм-аутом, но для потоков", вы передаете несколько массивов для чтения и записи, и при каждом вызове 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. Я сам пришел сюда, чтобы найти ответ, но один из обходных путей заключается в том, что PHP-программа, которая получает запрос от веб-сервера, делегирует всю формулировку ответа консольному приложению, которое сохраняет свои выходные данные, ответ на запрос, в двоичный файл. и PHP-программа, которая запустила консольное приложение, возвращает этот двоичный файл побайтно в качестве ответа на полученный запрос. Консольное приложение может быть написано на любом языке программирования, работающем на сервере, включая те, которые имеют надлежащую поддержку потоков, включая программы на C++, использующие OpenMP.
Один ненадежный, грязный трюк состоит в том, чтобы использовать PHP для запуска консольного приложения "uname",
uname -a
и распечатайте вывод этой консольной команды в вывод HTML, чтобы узнать точную версию серверного программного обеспечения. Затем установите точно такую же версию программного обеспечения в экземпляр VirtualBox, скомпилируйте / соберите все необходимые автономные, предпочтительно статические, двоичные файлы, а затем загрузите их на сервер. С этого момента приложение PHP может использовать эти двоичные файлы в роли консольного приложения, которое имеет правильную многопоточность. Это грязный, ненадежный обходной путь в ситуации, когда администратор сервера не установил на сервер все необходимые реализации языка программирования. Остерегайтесь того, что при каждом запросе PHP-приложение получает консольное (-ие) приложение (-ы) /exit/get_killed.
Что касается того, что администраторы хостинг-службы думают о таких шаблонах использования сервера, я думаю, это сводится к культуре. В Северной Европе поставщику услуг необходимо предоставить то, что было объявлено, и если было разрешено выполнение консольных команд и разрешена загрузка файлов, не относящихся к вредоносным программам, и поставщик услуг имеет право уничтожить любой процесс сервера через несколько минут или даже через 30 секунд. то у администраторов хостинг-сервиса нет никаких аргументов для формирования правильной жалобы. В Соединенных Штатах и Западной Европе ситуация / культура очень разные, и я считаю, что есть большой шанс, что в США и / или Западной Европе поставщик услуг хостинга откажется обслуживать клиентов услуг хостинга, которые используют описанный выше прием. Это только мое предположение, учитывая мой личный опыт работы с хостинг-услугами в США и то, что я слышал от других о хостинг-сервисах в Западной Европе. На момент написания моего текущего комментария (2018_09_01) я ничего не знаю о культурных нормах южно-европейских поставщиков услуг хостинга, администраторов южно-европейских сетей.
Может быть, я что-то пропустил, но exec не работал как асинхронный для меня в среде Windows, я использовал следующие в Windows, и это работало как шарм;)
$script_exec = "c:/php/php.exe c:/path/my_ascyn_script.php";
pclose(popen("start /B ". $script_exec, "r"));
Многопоточность означает выполнение нескольких задач или процессов одновременно, мы можем достичь этого в 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