Как можно использовать многопоточность в приложениях PHP

414

Существует ли реалистичный способ реализации многопоточной модели в PHP, будь то по-настоящему или просто имитировать ее. Некоторое время назад было предложено заставить операционную систему загружать другой экземпляр исполняемого файла PHP и обрабатывать другие одновременные процессы.

Проблема в том, что, когда PHP-код завершил выполнение, экземпляр PHP остается в памяти, потому что нет способа уничтожить его изнутри PHP. Так что, если вы моделируете несколько потоков, вы можете представить, что произойдет. Поэтому я все еще ищу способ, которым многопоточность может быть эффективно реализована или смоделирована изнутри PHP. Любые идеи?

Стив Оббайи
источник
1
Смотрите мой вопрос и ответы здесь: stackoverflow.com/questions/2101640/…
powtac
1
... и мой здесь: stackoverflow.com/questions/209774/does-php-have-threading/…
Франсуа Буржуа
как использовать расширение pthreads: phplobby.com/php-multi-thread-on-windows-pthreads-configuration
Мехмедов
Может быть интересен: pthreads.org
GibboK
Теперь, в 2020 году, кажется, что «параллель» php.net/manual/en/intro.parallel.php - это то, что мы хотим вместо «pthreads»: stackoverflow.com/a/56451969/470749
Райан,

Ответы:

432

Многопоточность возможна в 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);
}
ромовая баба
источник
3
@Baba, я не могу настроить и установить pthreads на сервере Xampp. Вы можете помочь мне с этим?
Ирфан
4
Загрузите бинарный файл windows здесь windows.php.net/downloads/pecl/releases/pthreads/0.0.45
Баба
17
Это хорошо, я не касался PHP годами, и теперь у него есть возможности многопоточности!
крейсер
1
Красиво и просто! Просто для справки, я развертываю приложение на сервере Azure Cloud Win, и если выбрана только базовая конфигурация с 1 ядром, многопоточность будет недоступна, пока не будет добавлено больше ядер.
Милан,
3
Пожалуйста, будьте осторожны: Джо Уоткинс, автор расширения pthreads, прекратил разработку в пользу нового параллельного расширения: github.com/krakjoe/pthreads/issues/929
Антон Белонович
42

почему ты не используешь 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]);
    }
}
masterb
источник
4
Я использую решение выше, и работает нормально, я думаю, что это был самый простой способ сделать параллельный процесс с использованием php.
Крестный отец
7
Как сказал @ e-info128, эта реализация разветвляет процесс, что означает, что он выполняется в другом процессе и не разделяет ресурсы процесса. При этом, если задание не нуждается в совместном использовании ресурсов, то это все равно будет работать и работать параллельно.
Раффи
1
Как бы вы передавали переменные в popen без использования переменных сеанса?
atwellpub
@atwellpub Ни в коем случае, это отдельные процессы, не разделяющие ресурсы. Даже сессии будут неудобным механизмом IPC
Cholthi Paul Ttiopic
1
Чтобы передать им данные, вы можете использовать аргументы и сервер Redis.
Амир Фо
21

Потоки не доступны в стандартном PHP, но параллельное программирование возможно при использовании HTTP-запросов в качестве асинхронных вызовов.

Если для параметра curl timeout установлено значение 1 и используется один и тот же session_id для процессов, которые вы хотите связать друг с другом, вы можете обмениваться данными с переменными сеанса, как в моем примере ниже. С помощью этого метода вы можете даже закрыть браузер, и параллельный процесс все еще существует на сервере.

Не забудьте проверить правильный идентификатор сессии, как это:

http: //localhost/test/verifysession.php? sessionid = [ правильный идентификатор]

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);
Ricardo
источник
4
В прошлый раз, когда я проверял (несколько лет назад), php не разрешал доступ к хранилищу файловых сессий двумя процессами одновременно. Он блокирует файл, и второй процесс должен сидеть в ожидании остановки первого скрипта. Я говорю о среде веб-сервера, а не CLI.
Алексей Теницкий
5
set_time_limit(0);Хлоп! Никогда, никогда не делай этого.
Кафосо
@ Kafoso Kafoso, почему бы и нет? Ну, я согласен на PHP в качестве процессора веб-скриптов, но почему бы не в CLI? Если что-то пойдет не так, CLI можно убить с помощью Ctrl + C ...
sijanec
14

Хотя вы не можете использовать потоки, у вас есть некоторый уровень контроля над процессами в 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.

JD Fitz.Gerald
источник
1
Этот ответ сейчас очень устарел (что очень справедливо, если учесть, что ему 11 лет). Посмотрите на pthreads ниже.
Мацей Папроцки
@MaciejPaprocki pThread больше не поддерживается с php 7.4, вместо этого используйте параллель
Эйри
10

использование потоков стало возможным благодаря расширению pthreads PECL

http://www.php.net/manual/en/book.pthreads.php

Pinkal Vansia
источник
pThread больше не поддерживается с 7.4 вместо использования параллельного
Airy
10

Я знаю, что это старый вопрос, но для людей, ищущих, есть расширение PECL, написанное на C, которое теперь дает возможность многопоточности PHP, оно находится здесь https://github.com/krakjoe/pthreads

JasonDavis
источник
Теперь pThread больше не поддерживается в php 7.4 вместо параллельного использования
Airy
5

Вы можете использовать exec () для запуска сценария командной строки (например, командной строки php), и если вы передадите вывод в файл, ваш сценарий не будет ждать завершения команды.

Я не совсем помню синтаксис php CLI, но вы бы хотели что-то вроде:

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

Я думаю, что для многих серверов общего хостинга exec () отключен по умолчанию из соображений безопасности, но, возможно, стоит попробовать.

Адам Хопкинсон
источник
4

Вы могли бы симулировать потоки. PHP может запускать фоновые процессы через popen (или proc_open). Эти процессы могут быть переданы через stdin и stdout. Конечно, эти процессы сами могут быть php-программами. Это, вероятно, так близко, как вы получите.

Пит
источник
3

В зависимости от того, что вы пытаетесь сделать, вы также можете использовать curl_multi для этого.

Sheldmandu
источник
3

Я знаю, что это старый, но вы могли бы посмотреть на http://phpthreadlib.sourceforge.net/

Он поддерживает двунаправленную связь между потоками, а также имеет встроенную защиту для уничтожения дочерних потоков (предотвращения сирот).

неподписанный
источник
3

Вы можете иметь возможность:

  1. multi_curl
  2. Можно использовать системную команду для того же
  3. Идеальный сценарий - создать функцию потоков на языке Си и скомпилировать / настроить на PHP. Теперь эта функция будет функцией PHP.
Манодж Донга
источник
3

Класс Thread доступен, так как PECL pthreads ≥ 2.0.0.

Дхананджай Кашьяп
источник
1
я могу запустить поток по HTTP? просто: yourname.com/thread.php?
Мое имя
Теперь pThread больше не поддерживается с php 7.4, вместо этого используйте параллель
Airy
3

Как насчет 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
    }

?>
Джаррод
источник
2

pcntl_forkне будет работать в среде веб-сервера, если включен безопасный режим . В этом случае он будет работать только в CLI-версии PHP.

Stilero
источник
1

Если вы используете сервер 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();
Юрий Ярвинен
источник
-1

На момент написания моего текущего комментария я не знаю о потоках PHP. Я сам пришел сюда, чтобы найти ответ, но один из обходных путей заключается в том, что PHP-программа, которая получает запрос от веб-сервера, делегирует всю формулировку ответа консольному приложению, которое сохраняет свои выходные данные, ответ на запрос, в двоичный файл. и программа PHP, которая запустила консольное приложение, возвращает этот двоичный файл побайтно в качестве ответа на полученный запрос. Консольное приложение может быть написано на любом языке программирования, работающем на сервере, включая те, которые имеют надлежащую поддержку потоков, включая программы на C ++, использующие OpenMP.

Один ненадежный, грязный трюк состоит в том, чтобы использовать PHP для запуска консольного приложения "uname",

uname -a

и распечатайте вывод этой консольной команды в вывод HTML, чтобы узнать точную версию серверного программного обеспечения. Затем установите точно такую ​​же версию программного обеспечения на экземпляр VirtualBox, скомпилируйте / соберите все необходимые автономные, предпочтительно статические, двоичные файлы, а затем загрузите их на сервер. С этого момента приложение PHP может использовать эти двоичные файлы в роли консольного приложения, которое имеет правильную многопоточность. Это грязный, ненадежный обходной путь в ситуации, когда администратор сервера не установил на сервер все необходимые реализации языка программирования. Остерегайтесь того, что при каждом запросе PHP-приложение получает консольное (-ие) приложение (-ы) / exit / get_killed.

Что касается того, что администраторы хостинг-службы думают о таких шаблонах использования сервера, я думаю, это сводится к культуре. В Северной Европе поставщику услуг необходимо предоставить то, что было объявлено, и если было разрешено выполнение консольных команд и разрешена загрузка файлов, не относящихся к вредоносным программам, и поставщик услуг имеет право уничтожить любой процесс сервера через несколько минут или даже через 30 секунд. то у администраторов хостинг-сервиса нет никаких аргументов для формирования правильной жалобы. В Соединенных Штатах и ​​Западной Европе ситуация / культура очень разные, и я считаю, что есть большой шанс, что в США и / или Западной Европе поставщик услуг хостинга откажется обслуживать клиентов услуг хостинга, которые используют описанный выше прием. Это только мое предположение, учитывая мой личный опыт общения с США. услуги хостинга и учитывая то, что я слышал от других о хостингах Западной Европы. На момент написания моего текущего комментария (2018_09_01) я ничего не знаю о культурных нормах южно-европейских поставщиков услуг хостинга, администраторов южно-европейских сетей.

Мартин Вахи
источник
-3

Может быть, я что-то пропустил, но exec не работал как асинхронный для меня в среде Windows, я использовал следующие в Windows, и это работало как шарм;)

$script_exec = "c:/php/php.exe c:/path/my_ascyn_script.php";

pclose(popen("start /B ". $script_exec, "r"));
Mubashar
источник
1
Чтобы взломать многопоточность в PHP, вы должны открыть несколько операторов popen () (PHP не будет ждать его завершения). Затем через полдюжины или около того времени вы вызываете pclose () для них (PHP будет ждать завершения pclose ()). В вашем коде вы закрываете каждый поток сразу после его открытия, чтобы ваш код не вел себя как многопоточный.
Kmeixner
-5

Многопоточность означает выполнение нескольких задач или процессов одновременно, мы можем достичь этого в 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

Пир Абдул
источник
Кроме того, это не имеет ничего общего с MultiThreading. Это параллельная обработка. Совершенно разные вещи.
Цифровой человек
На мой взгляд, в качестве обходного пути, экстренного взлома, идея предлагаемого решения очень уместна, но я предполагаю, что у разных людей могут возникать пламенные войны из-за того, что составляет «истинную многопоточность», потому что существует различие между параллелизмом и аппаратным параллельная обработка, как описано на сайте
Мартин Вахи,