Pull to refresh

Comments 19


Спасибо за статью, интересно. Но всё же, как надоел это пример про генераторы и корутины с чтением файла по строкам!
"Конечно, нужно отметить, что мы получаем возможность работы с
потенциально бесконечными задачами, что без генераторов невозможно."


Но почему-то всегда приводится этот пример, который элементарно переписывается без всяких генераторов. При этом, также останется чтение по строкам, без считывания всего файла в память. И в обычной работе, в коде вроде порой хочется ввернуть генератор. Даже напишешь, а потом понимаешь что можно проще и понятнее без него.

Может есть какой-то пример, где действительно без них никак, и есть настоящая польза?

Может есть какой-то пример, где действительно без них никак, и есть настоящая польза?

Конечно есть. Представьте себе файл со списком недействительных паспортов: https://проверки.гувм.мвд.рф/upload/expired-passports/list_of_expired_passports.csv.bz2

Всего-то 500+ мегабайт CSV в архиве. Сделайте его обработку без генераторов и со считыванием всего файла в память.

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

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

Вы "ищете блох" в конкретном примере, утверждая, что ложку можно вырезать из полена и без рейсфедера.

Я же вам рассказываю - что такое рейсфедер и как его применять, безотносительно ложки.

Причем тут ваш пример?

Причем тут ваш пример?

Это не мой пример, это ваш (и не только) пример. Он хорошо показывает "как работают генераторы" и плохо показывает "как получить пользу от применения генераторов".
Я всего лишь спросил, надеясь на ваш богатый опыт, нет ли другого примера, где использование генератора приводит к реальной экономии ресурсов, которой не достичь без оного. Ну нет, так нет.

Попробую ответить самому себе, какое у меня к данному моменту сложилось впечатление (но может, у кого-то еще найдется пример):
- Чтобы реализовать аналогичный функционал без генератора, вам придется или использовать свой объект с состоянием, или вынести весь код в тело цикла, где и будет состояние выполнения тех или иных операций. В первом случае все довольно просто, но дополнительная писанина и размножение сущностей. Во втором случае - может получиться сложночитаемая каша. Таким образом главная выгода от генераторов - сокращение и упрощение кода, но вовсе не экономия ресурсов. И выгода тем больше, чем больше этаких "задач с состоянием" мы пытаемся выполнить за такт цикла, и чем больше кода в каждой "задаче".

https://www.php.net/manual/ru/function.fgets.php
Внезапно, здесь пример чтения файла без генераторов (размер файла значения не имеет, потому что чтение будет происходить построчно).
Присоединяюсь к вопросу: есть пример использования генераторов не про чтение файлов?

Может есть какой-то пример, где действительно без них никак, и есть настоящая польза?

С одной стороны - нет таких примеров, т.к. генератор = цикл, а значит тело генератора всегда можно развернуть в обычный цикл.

С другой стороны - нельзя передать цикл из одного обработчика в другой, а генератор можно. Да, конечно можно обойтись if/switch и получить обычный цикл, но с генераторами получится проще код.

На примере покер-калькулятора: представьте, что у вас есть метод, считающий комбинацию из набора 5 карт, т.е. на вход 5 карт, на выходе - какая это комбинация.

И есть 2 типа игры: холдем и омаха. Чуток информации для тех, кто не в курсе правил: в холдеме на руках 2 карты + 5 на столе, берем из этих 7и 5 с максимальной комбинацией. В омахе 4 на руках (про другие типы тоже знаю, но сейчас не об этом) + 5 на столе, всего 9, но с ручных учитываются только любые 2.

Если наш калькулятор работает с обоими типами игр - придется в цикле делать цикл для генерации всех вариантов с рук + то же самое, что в холдеме. Код сильно усложняется для чтения и поддержки.

В случае генераторов всё это можно вынести в отдельные методы, которые вернут генератор, который будет генерировать данные по 7 карт. В итоге, основной цикл общий, простой.

Возможно ли такое в PHP с использованием стандартного синтаксиса языка и стандартной библиотеки?

Нет.

Ну как сказать, вот набросал на коленке по-быстрому: https://github.com/SerafimArts/simple-interval-example


P.S. А, дочитал до момента где описывается React c почти аналогичной реализацией. Зря получается пример набрасывал: "Коммент пиши — статью не читай", ага.


Тогда я не понял почему в этом тезисе "нет" написано? Что стандартными средствами языка подобное невозможно.

Кирилл, я имел в виду, что в стандартной библиотеке нет готового "setInterval" или иных инструментов, чтобы отложить задачу.

А за пример спасибо, интересно.

Кирилл, я имел в виду, что в стандартной библиотеке нет готового "setInterval" или иных инструментов, чтобы отложить задачу.

Давай я чуток подушню тогда)))


Чтобы именно "отложить" задачу — могу назвать как минимум 3 сходу:


Через GC (выполнится во время подчистки мусора)
$deferred = new class {
    public function __destruct() {
        echo 'Deferred';
    }
};

echo 'Some ';

// Some Deferred

Внедрение в 'тики' VM
<?php
declare(ticks=1);

register_tick_function(function () {
    echo 'ТАЙМЕР';
});

echo 'делаем';
echo 'какую-то';
echo 'фигню';

// ТАЙМЕРделаемТАЙМЕРкакую-тоТАЙМЕРфигнюТАЙМЕР

Через хендлер завершения процесса
<?php

register_shutdown_function(function () {
    echo 'Я всё';
});

echo 'Привет';
echo 'Мир';
// ПриветМирЯ всё

Ты не душнишь, ты экзотику накидываешь, за что тебе большое спасибо! ))

Всё же хочется откладывать до наступления кастомного события, а не в прибитый гвоздями к жизненному циклу хендлер. Вот тики, кстати, можно заиспользовать совместно с глобальными переменными, но это будет "лекарство хуже болезни" :)

Hidden text
<?php
declare(ticks=1);

$ticks = 0;
$break = null;

register_tick_function(
    function () {
        global $ticks, $break;
        $ticks++;
        if ($break != null && $ticks > $break) {
            die("DIE MOTHERFUCKER, DIE!!!");
        }
    }
);

while (true) {
    sleep(1);
    echo "I'm alive for tick $ticks" . PHP_EOL;

    if (rand(0, 100) > 50) {
        echo "Death looking for you" . PHP_EOL;
        $break = $ticks + 2;
    }
}

Вместо глобальных переменных можно использовать статические методы какого-либо класса, как, кстати, делает Fiber.

https://www.php.net/manual/ru/intro.pcntl.php только для cgi/cli. что сильно ограничивает применение. https://stackoverflow.com/questions/35026153/call-to-undefined-function-pcntl-fork-php-fpm-nginx#answer-35029409 комментарий от Joe Watkins.

parallel погиб - комментарий от тогоже Joe Watkins https://github.com/krakjoe/parallel/issues/201#issuecomment-892442141

по факту нужно смотреть в сторону swoole/amphp

Интересная статья, спасибо.

Думая о параллельности, хотел спросить. А если я из php кода инициализирую другой http запрос на свой же скрипт, например с другими get-параметрами, и не буду ждать ответа, то получается сервер сам создаст новый процесс в параллель и будет одновременное выполнение?, и таким образом тоже можно запустить задачи в несколько потоков?

или нет возможности отправить запрос и не ждать ответа?

Можно, но так не делают обычно из-за накладных расходов.

Создать неблокирующий http запрос на endpoint некого ресурса встроенными средствами PHP можно

  1. Через библиотеку curl, а именно можно смотреть в сторону функции curl_multi_init()

  2. Либо общением через сокет и установку неблокирующего режима, см. функцию socket_set_nonblock

Но на практике это означает проблемы:

  1. Обычно нужно убедится, что endpoint принял запрос, поэтому код придется формировать, либо в асинхронной манере, либо "лапшично" некрасивым образом.

  2. Такой подход - это полный цикл: нахождение хоста, стук к веб-серверу, поднятие процесса PHP (если мы говорим про endpoint тоже на PHP) и т.п. В общем это не выгодно по ресурсам и гораздо дольше нежели форкнуть процесс, создать поток или запихнуть задачу в очередь и выполнить асинхронно.

Включение неблокирующего режима доступно для любых стримов, не обязательно сокетов. Можно и через fopen:


$fp = fopen('http://path-to.site', 'r');
stream_set_blocking($fp, false);
Sign up to leave a comment.

Articles