Comments 20
Статья - просто бомба! Использование (относительно) новой фичи в РНР, а не пережёвывание старого. Практически реальный, а не совсем академический пример. Толковая подача. Спасибо большое! Всем корпоративным блогам брать пример со СберЗдоровья!
(͡°͜ʖ͡°)
А сравнивали с тем же го? Было необходимо асинхронно запросить несколько ендпоинтов за раз - газл даже в асинхронном режиме сильно проигрывает горутинам
Конкретно это решение с Go не сравнивали, так как не было такой задачи.
Решение описанное в статье - это попытка добавить асинхронность для http-запросов в уже существующие сервисы не сильно меняя код, архитектуру и языки программирования)
Удаленный имеет смысл делать асинхронно только если он медленный. В таком сценарии в принципе выигрышь go будет минимальное если будет вообще. И вообще, почему go в не раст.
А существуют ли какие нибудь удобные библиотеки, чтобы было не так низкоуровнево?
У вас неправильно работает Await::all
. Сейчас вы просто продолжаете каждый файбер после запуска, даже если ответ на запрос еще не пришел. Смысл в Fiber::suspend
и Fiber::resume
в том, чтобы их вызвать в определенное время. Например, Fiber::suspend
вызвать после того, как мы отправили запрос по сети, а Fiber::resume
после того, как мы узнаем, например через stream_select
, что ответ пришел – и мы можем продолжить корутину дальше, чтобы она могла прочитать данные из сокета.
Действительно, можно сделать так как вы говорите, но для решения задачи, которая перед нами стояла это не требуется. Смысл функций suspend и resume в том, чтобы приостановить файбер, а когда это сделать (после отправки запроса по сети или после постановки в очередь, но до отправки по сети) - это уже дело конкретной реализации. В нашем случае с Guzzle есть возможность сделать так как приведено, потому что Guzzle использует curl_multi_exec
. Для других задач, конечно может потребоваться stream_select
или что-то подобное и функция Await::all
представленная в статье будет непригодна.
В примечании к функции Await::all
я специально это отметил.
Действительно, можно сделать так как вы говорите, но для решения задачи, которая перед нами стояла это не требуется
Я не понимаю, какая перед вами стояла задача и почему вы считаете текущий код решением. Вы просто не используете ровно никаких преимуществ файберов. Вы сначала приостановили код, а потом сразу же запустили опять, хотя промис газзла мог быть еще не готов к завершению. Чтобы код имел смысл, вы должны узнать, что промис завершился, и тогда вызывать Fiber::resume
.
Вы можете поставить газзл, взять пример из статьи и убедиться, что 2 запроса будут отправлены одновременно.
У меня в статье написано следующее:
Метод
sendAsync
не отправляет запрос сразу, он ставит его в очередь внутри Guzzle, а ужеwait
берёт все запросы из очереди и отправляет их с помощью функции curl_multi_exec
Попытка объяснить «на пальцах»: Файбер приостанавливается после добавления первого запроса в очередь газзла. После этого запускается второй файбер который делает тоже самое и также приостанавливается. К этому моменту в очереди газзла 2 неотправленных запроса. После, первый файбер продолжается вызывая метод wait у первого промиса. Вызов wait запускат отправку ВСЕХ запросов, которые есть в очереди газзла, независимо от того у какого промиса этот wait вызван. В этот момент поток фактически блокируется, пока нужный промис не разрешится. Далее, когда промис разрешился файбер доходит до конца. Следом происходит продолжение выполнения второго файбера, у второго промиса также вызывается метод wait. Так как в очереди в этот момент нет запросов на отправку, метод wait либо сразу же вернет результат (если он уже готов), либо дождется пока результат нужного промиса придёт.
Вот так это работает. Вы можете сами это проверить скопировав пример метода Await::all из статьи.
Я не ответил на вопрос какая стояла задача.
Задача была добавить асинхронность для http-запросов в уже существующие сервисы не сильно меняя код и архитектуру.
По поводу теории - (а)синхронность, параллелизм и многозадачность свалены в кучу.
Правильнее разделять так:
(А)синхронность.
PHP по дефолту синхронный, то есть код исполняется последовательно, в порядке вызова.
Асинхронное выполнение означает, что каждая функцая (метод, со-программа) может вызываться и отрабатывать (завершать работу) не в том порядке, в каком была вызвана.
Что касается цитаты
PHP и асинхронность. Такая комбинация долгие годы казалась невозможной, ведь PHP прочно ассоциировался с блокирующим подходом и синхронным выполнением скриптов «от запроса до ответа»
Такая комбинация всегда была возможной через написание менеджера вызовов и обработки по типу event loop как в javascript (js оказывается под капотом тоже синхронный!). Но до поры до времени мейнтейнерам пыхи хватало ума держаться подальше от такой асинхронности.
Многозадачность (конкурентность).
Многозадачность это способ разделить ресурсы между со-программами так, чтобы всем досталось понемногу. Например, процессорное время по какому-то правилу распределяется между со-программами (или процессами), таким образом каждая со-программа за каждую секунду работы получает какое-то процессорное время. Вытесняющая многозадачность это когда правило распределения - условно, пропорция (каждой со-программе даем столько-то квантов, % процессорного времени), а кооперативная - когда со-программа отрабатывает и сама освобождает ресурс для следующей ожидающей со-программы.
Параллелизм.
Это когда со-программы/процессы выполняются параллельно, то есть без прямой зависимости друг от друга. Например, на разных компьютерах или на разных ядрах одного компьютера.
Обратите внимание, можно реализовать (а)синхронность или многозадачность и на одном ядре, но параллелзим возможен только при наличии более чем одного вычислительного узла (например ядра проца).
Только Fiber это всего лишь синтаксический сахар, а не асинхронщина в классическом его смысле. Не будь у нее асинхронных вызовов, например, curl_multi_exec или pg_send_query вы бы не добились одновременно выполнения двух запросов, например, через curl или PDO.
То есть они могут запустить некую операцию, остановиться, отдать управление другим функциям, а когда результат будет готов, программа переключится обратно в эту функцию, и она продолжит свое выполнение с того места, где остановилась.
Т.е. Fiber просто заменяет цикл while. Разве что код немного красивее становится
синие функции могут вызывать как синие, так и красные, а красные могут вызывать только красные.
Или тут неоднозначное определение или ты не до конца разобрался. Синие функции можно вызывать в красных функциях, а вот красные в синих нет. await нельзя использовать в синхронной функции.
Давайте заменим в цитате "синие" на синхронные, "красные" на асинхронные.
Получим текст:
синхронные функции могут вызывать как синхронные, так и асинхронные, а асинхронные могут вызывать только асинхронные.
Если мы вызываем в синхронной функции асинхронную, то мы просто дожидаемся её результата. Если говорить про JS, то да, await
вызвать нельзя, но с помощью then
у Promise
можно. "Природа" синхронной функции от этого не меняется. Если наоборот, то асинхронная функция перестаёт быть асинхронной, ведь ей придётся заблокироваться вызвав синхронную функцию. Соответственно "природа" асинхронной функции меняется на синхронную.
В этом определении больше речь про "природу" функций, а не про возможность конкретных вызовов в разных языках.
Советую перечитать статью Боба. Всё как раз наоборот. Красная функция окрашивает функцию первого порядка в красный. Т.е. если синхронная функция вызывает асинхронную, то она должна стать также асинхронной. "Краснота" заразна.
А вот асинхронная функция может вызывать синхронную. Да, измениться так называемая природа, но ошибки не будет. А в обратном случае будет ошибка, так как асинхронная функция возвращает "обещание", а не результат. Т.е. синхронная функция получит промис, выполнит действие над ним, а не над результатом, и завершится не дожидаясь завершения асинхронной функции.
синхронные функции могут вызывать как синхронные, так и асинхронные, а асинхронные могут вызывать только асинхронные.
Опять же получилось неоднозначно. Непонятно кто кого вызывает. Где подлежащее - слева от глагола "вызывать" или справа? Если бы было написано, что "синхронные функции могут вызываться как в синхронных, так и в асинхронных", то тут нет двусмысленности. И это определение правильное. Но твое дальнейшее объяснение говорит от том, что смысл в обратном, что синхронные функции могут вызывать внутри себя как синхронные, так и асинхронные. Что не соответствует теории цветных функций.
Пожалуй соглашусь, что описание и мой комментарий выше как-то неверно звучал.
Кажется у меня смешалось две вещи: изменение "природы" функций и возможности вызовов.
С точки зрения теории цветных функций, которую описывал Боб, конечно, синюю(синхронную) функцию можно вызвать откуда угодно, а вот красную (асинхронную) только из красной.
С точки зрения природы(кажется про неё Боб вовсе не писал) кажется все верно. Если мы вызываем в асинхронной функции синхронную, то асинхронная функция становится по сути синхронной, то есть меняется природа и по этой логике в асинхронных функциях вызывать синхронные "нельзя"(можно, но нет смысла).
Спасибо за замечание, подумаю как перефразировать в статье этот момент.
Использование файберов в PHP: разбор от команды СберЗдоровье