Как использовать несколько асинхронных файлов с волокнами в PHP?

Я хотел бы получить содержимое из каждого URL-адреса в списке, используя fread и Fibers, где каждому потоку не нужно ждать feof для запуска другого fread в другом URL-адресе.

Мой текущий код следующий:

<?php

function getFiberFromStream($stream, $url): Fiber {
    
    return new Fiber(function ($stream) use ($url): void {
                while (!feof($stream)) {
                    echo "reading 100 bytes from $url".PHP_EOL;
                    $contents = fread($stream, 100);
                    Fiber::suspend($contents);
                }
            });
}

function getContents(array $urls): array {

    $contents = [];

    foreach ($urls as $key => $url) {

        $stream = fopen($url, 'r');
        stream_set_blocking($stream, false);
        $fiber = getFiberFromStream($stream, $url);
        $content = $fiber->start($stream);

        while (!$fiber->isTerminated()) {
            $content .= $fiber->resume();
        }
        fclose($stream);

        $contents[$urls[$key]] = $content;
    }

    return $contents;
}

$urls = [
    'https://www.google.com/',
    'https://www.twitter.com',
    'https://www.facebook.com'
];

var_dump(getContents($urls));

К сожалению, эхо, используемое в getFiberFromStream(), показывает, что этот текущий код ожидает получения всего содержимого из URL-адреса, чтобы перейти к следующему:

reading 100 bytes from https://www.google.com
reading 100 bytes from https://www.google.com
reading 100 bytes from https://www.google.com //finished
reading 100 bytes from https://www.twitter.com
reading 100 bytes from https://www.twitter.com
reading 100 bytes from https://www.twitter.com //finished
reading 100 bytes from https://www.facebook.com
[...]

Я хотел бы что-то вроде:

reading 100 bytes from https://www.google.com
reading 100 bytes from https://www.twitter.com
reading 100 bytes from https://www.facebook.com
reading 100 bytes from https://www.google.com
reading 100 bytes from https://www.twitter.com
reading 100 bytes from https://www.facebook.com
[...]

Знаете ли вы, что можете использовать curl для выполнения параллельные запросы?

Olivier 30.03.2022 09:19

Да, спасибо, но я хотел бы использовать Fibers в учебных целях.

celsowm 30.03.2022 13:34

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

MikeT 30.03.2022 17:02
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
3
61
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Решение здесь состоит в том, чтобы запустить все волокна для всех URL-адресов сразу и только после этого опрашивать их.

Попробуйте что-то вроде этого:


function getContents(array $urls): array {

    $contents = [];
    $fibers = [];

    // start them all up
    foreach ($urls as $key => $url) {

        $stream = fopen($url, 'r');
        stream_set_blocking($stream, false);
        $fiber = getFiberFromStream($stream, $url);
        $content = $fiber->start($stream);

        // save fiber context so we can process them later
        $fibers[$key] = [$fiber, $content, $stream];
    }

    // now poll
    $have_unterminated_fibers = true;
    while ($have_unterminated_fibers) {

        // first suppose we have no work to do
        $have_unterminated_fibers = false;

        // now loop over fibers to see if any is still working
        foreach ($fibers as $key => $item) {
            // fetch context
            $fiber = $item[0]; 
            $content = $item[1]; 
            $stream = $item[2];

            // don't do while till the end here, 
            // just process next chunk
            if (!$fiber->isTerminated()) {
                // yep, mark we still have some work left
                $have_unterminated_fibers = true;

                // update content in the context
                $content .= $fiber->resume();
                $fibers[$key][1] = $content;
            } else {
                if ($stream) {
                    fclose($stream);

                    // save result for return
                    $contents[$urls[$key]] = $content;

                    // mark stream as closed in context 
                    // so it don't close twice
                    $fibers[$key][2] = null;
                }
            }
        }
    }

    return $contents;
}

Другие вопросы по теме