Не удается заставить обещания ReactPHP выполняться асинхронно

У меня есть PHP-скрипт, который обрабатывает данные, загруженные из нескольких REST API, в стандартизированный формат и создает массив или таблицу этих данных. В настоящее время скрипт выполняет все синхронно и поэтому занимает слишком много времени.

Я пытался научиться выполнять функцию, которая извлекает и обрабатывает данные одновременно или асинхронно, чтобы общее время было временем самого медленного вызова. Из моих исследований следует, что ReactPHP или Amp являются правильными инструментами.

Однако мне не удалось создать тестовый код, который действительно выполняется правильно. Простой пример прилагается, где mysquare() представляет мою более сложную функцию. Из-за отсутствия примеров в сети именно того, чего я пытаюсь достичь, я был вынужден использовать метод грубой силы с 3 примерами, перечисленными в моем коде.

Q1: Использую ли я правильный инструмент для работы?

Q2: Можете ли вы исправить мой пример кода, чтобы он выполнялся асинхронно?

NB: я настоящий новичок, поэтому будет оценен самый простой пример кода с минимумом высокоуровневого жаргона программирования.

<?php
require_once("../vendor/autoload.php");

for ($i = 0; $i <= 4; $i++) {

    // Experiment 1
    $deferred[$i] = new React\Promise\Deferred(function () use ($i) {
        echo $x."\n";
        usleep(rand(0, 3000000));  // Simulates long network call
        return array($x=> $x * $x);
    });

    // Experiment 2
    $promise[$i]=$deferred[$i]->promise(function () use ($i) {
        echo $x."\n";
        usleep(rand(0, 3000000));  // Simulates long network call
        return array($x=> $x * $x);
    });

    // Experiment 3
    $functioncall[$i] = function () use ($i) {
        echo $x."\n";
        usleep(rand(0, 3000000));  // Simulates long network call
        return array($x=> $x * $x);
    };
}

$promises = React\Promise\all($deferred); // Doesn't work
$promises = React\Promise\all($promise); // Doesn't work
$promises = React\Promise\all($functioncall); // Doesn't work

// print_r($promises);  // Doesn't return array of results but a complex object

//  This is what I would like to execute simulatenously with a variety of inputs
function mysquare($x)
{
    echo $x."\n";
    usleep(rand(0, 3000000));  // Simulates long network call
    return array($x=> $x * $x);
}

person Viktorius    schedule 13.07.2018    source источник
comment
Кроме того, скрипт всегда запускается из интерфейса командной строки.   -  person Viktorius    schedule 13.07.2018


Ответы (2)


Асинхронность не означает, что несколько потоков выполняются параллельно. 2 функции могут работать только «в одно и то же время», если они (например) выполняют ввод-вывод, такой как HTTP-запрос.

usleep() блокирует, поэтому вы ничего не получаете. И ReactPHP, и Amp будут иметь какую-то «спящую» функцию, встроенную прямо в цикл обработки событий.

По той же причине вы не сможете просто использовать curl, потому что он также будет блокироваться из коробки. Вам нужно использовать библиотеки HTTP, которые React и Amp предоставляют и/рекомендуют.

Поскольку вашей конечной целью является выполнение HTTP-запросов, вы можете не использовать ни одну из этих платформ и использовать только функции curl_multi. Хотя они немного сложны в использовании.

person Evert    schedule 13.07.2018
comment
Спасибо @Evert. Я не могу использовать curl_multi или одну из включенных HTTP-библиотек, потому что мой код зависит от другой библиотеки, которая сама вызывает CURL_GET и т. д. Поэтому мне нужно иметь возможность одновременно выполнять несколько вызовов myfunction(). - person Viktorius; 13.07.2018
comment
по ссылке. Вот почему многопоточность не является хорошим решением в такой среде. Если вы ищете многопоточность как решение задач, блокирующих ввод-вывод (например, выполнение HTTP-запросов), позвольте мне указать вам направление асинхронного программирования, которого можно достичь с помощью таких фреймворков, как Amp. - person Viktorius; 13.07.2018
comment
@Viktorius, если вы застряли в использовании другой библиотеки, которая выполняет HTTP-запросы, я бы сказал, что вероятность того, что эта библиотека выполняет асинхронные вызовы, составляет 99%, и асинхронная среда здесь вам не поможет. Асинхронные фреймворки зависят от библиотек, которые передают управление обратно своему циклу обработки событий, когда они ожидают ввода-вывода (сетевого/дискового и т. д.) для выполнения своей работы. - person Evert; 13.07.2018
comment
Итак, если это ваши ограничения, я бы предложил посмотреть в другом направлении: можете ли вы просто запустить несколько процессов PHP, каждый из которых выполняет часть работы? - person Evert; 13.07.2018
comment
библиотека использует стандартные вызовы curl по link, и у меня есть видел, как разработчики обсуждали добавление асинхронности в библиотеку php, но не решили, какую платформу использовать. Так что пока не реализовано. - person Viktorius; 16.07.2018
comment
Вы можете сделать асинхронность с PHP сегодня, иначе такие фреймворки, как ReactPHP, были бы невозможны. Дело в том, что вам нужна поддержка библиотеки. У вашей библиотеки нет поддержки, поэтому вы не можете этого сделать. - person Evert; 16.07.2018

Я отвечаю на свой вопрос, пытаясь помочь другим пользователям, однако это решение было разработано в одиночку без помощи опытного программиста, и поэтому я не знаю, в конечном счете, это лучший способ сделать это.

TL;DR

Я переключился с ReactPHP, потому что не понял его, на использование amphp/parallel-functions, который предлагает упрощенный интерфейс конечного пользователя... пример кода, использующего этот интерфейс, прилагается.

<?php
require_once("../vendor/autoload.php");

use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;

$start = \microtime(true);

$mysquare = function ($x) {
    sleep($x);  // Simulates long network call
    //echo $x."\n";
    return $x * $x;
};

print_r(wait(parallelMap([5,4,3,2,1,6,7,8,9,10], $mysquare)));

print 'Took ' . (\microtime(true) - $start) . ' milliseconds.' . \PHP_EOL;

Код примера выполняется за 10,2 секунды, что немного дольше, чем самый продолжительный экземпляр $mysquare().

В моем реальном случае использования я смог получить данные через HTTP из 90 отдельных источников примерно за 5 секунд.

Заметки:

Библиотека amphp/parallel-functions, похоже, использует потоки под капотом. Судя по моему предварительному опыту, для этого требуется гораздо больше памяти, чем для однопоточного PHP-скрипта, но я еще не оценил все последствия. Это было выделено, когда я передавал большой массив в $mysquare с помощью выражения use ($myarray), а размер массива составлял 65 МБ. Это привело к остановке кода и экспоненциальному увеличению времени выполнения настолько, что потребовалось на несколько порядков больше, чем при синхронном выполнении. Кроме того, использование памяти превысило 5G! в какой-то момент я подумал, что amphp дублирует $myarray для каждого экземпляра. Переработка моего кода, чтобы избежать использования выражения ($myarray), устранила эту проблему.

person Viktorius    schedule 16.07.2018
comment
Вы правы, amphp/parallel-functions использует потоки/дочерние процессы под капотом. Поскольку PHP ничем не делится, все, что передается между потоками/дочерними процессами, должно быть сериализовано и передано между рабочими процессами. Он использует больше памяти, чем неблокирующая реализация ввода-вывода с одним циклом событий, но это другой подход, потому что иногда невозможно заменить блокирующую реализацию. С помощью amphp/parallel его можно переместить в рабочий пул, чтобы он не блокировал оставшееся приложение. - person kelunik; 03.08.2018