PHP Profi

Асинхронный PHP: Зачем? Перевод

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

Отзывчивость

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

Параллельность vs асинхронность

Большинство людей, когда они видят асинхронный код, сразу думают “О, это круто! Я могу запустить мои задачи параллельно!”. Я, может вас разочарую, но на самом деле, это неправда, конкурентность (асинхронность) и параллельность — это не то же самое. Это часто встречающаяся ошибка, поэтому давайте попробуем разобраться почему.

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

Асинхронность:

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

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

async-execution

Параллельность:

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

parallel-execution

Чтобы проиллюстрировать разницу между асинхронным и параллельным выполнением на реальных примерах, мы можем сравнить два популярных веб-сервера: Apache и Nginx. Они прекрасно иллюстрируют эту разницу: Nginx является асинхронным и основан на событиях, в то время как Apache использует параллельные потоки. Apache создает новые потоки для каждого дополнительного подключения, поэтому существует максимально допустимое количество соединений в зависимости от доступной памяти в системе. При достижении этого лимита подключений, Apache отказывается от дополнительных соединений. Ограничивающим фактором в настройке Apache является память (помните, что параллельное выполнение часто зависит от аппаратного обеспечения). Если поток останавливается, клиент ждет ответа, пока поток освободится и вернёт ответ.

Nginx работает иначе, чем Apache и не создаёт новые потоки для каждого входящего запроса. У него есть основной процесс-воркер (или несколько процессов, зачастую на практике рекомендуется иметь по одному воркеру на каждый процессор(CPU)), который является однопоточным. Этот воркер может обрабатывать тысячи одновременных подключений. Он делает это асинхронно с одним потоком, а не с помощью многопоточного параллельного выполнения.

Итак, асинхронность (или конкурентность) — это способ выполнения задач. Она представляет собой композицию из независимо выполняющихся задач. Параллельность — это одновременное выполнение нескольких задач (они могут быть связаны, а могут и нет). В асинхронности, мы имеем дело со многими разными задачами одновременно. Параллельность делает много задач сразу. Звучит одинаково, но в основе разные идеи. Конкурентность — это о структуре, в то время как параллельность — об исполнении.

От переводчика:

Асинхронность: у вас есть бекенд задача, которую вы делаете в данный момент. Тут к вам обратился фронтенд разработчик с просьбой немого поправить API. Вы сохранились у себя в ветке, переключились на другую (в данный момент вы заняты задачей с API), поправили и вернулись к своей задаче. После вас позвали на совещание по какой-то тематике, вы прервались, сходили (в данный момент вы выполняете третью задачу, которая будет продолжена на следующем совещании) и вернулись опять к своей первой задаче. Эти три задачи вы выполняли асинхронно — другими словами, прерываясь и переключаясь между задачами, выделяя на них понемногу времени.

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

Зачем на бекенде?

Теперь вы можете возмутиться на то, что на беке вас не особо волнует отзывчивость. У вас на фронте есть все эти гадкие вещи типа асинхронного JavaScript-а, а всё, что должен делать ваш сервер, — это просто отвечать на запросы. Так что обеспечить отзывчивость пользователю — это задача фронта, а не ваша. Да, это правда, но бекенд не ограничивается только API-ответами. Иногда вам нужно управлять какими-то сложными задачами, например, сервер для загрузки видео. В данном случае, возможно, отклик не является ключевым фактором, но мы упираемся в нехватку ресурсов, потому что приложение должно ждать. Он может ждать операций файловой системы, сетевого подключения, запросов к базе и так далее. Часто эти операции ввода/вывода осуществляются крайне медленно по сравнению с расчетами на CPU, например, когда мы конвертируем видеофайлы. А пока мы медленно сохраняем или читаем файл, наш процессор должен ждать и ничего не делать, вместо того, чтобы делать какую-то полезную работу. Как мы уже говорили, вместо ожидания, мы можем выполнять эти задачи в фоновом режиме. Как? Читайте ниже.

Асинхронный РНР

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

setTimeout(function() {
    console.log('After timeout');
}, 1);

console.log('Before timeout');

При выполнении этого кода мы видим следующее:

Before timeout
After timeout

Функция setTimeout() ставит в очередь код, который будет выполняться после завершения текущего стека вызовов. Это означает, что мы нарушаем синхронный поток кода и откладываем выполнение некоторой части кода. Второй вызов console.log() будет выполнен до первого (внутри функции settimeout()), который был поставлен в очередь.

Но как на счёт PHP? Ну, в PHP из коробки у нас нет хороших и удобных инструментов для написания действительно асинхронного кода. Нет функции эквивалентной settimeout() и мы просто не можем отложить или поставить в очередь какой-то код. Вот почему такие фреймворки и библиотеки, как Amp и ReactPHP, начали появляться. Их основная идея в том, чтобы скрыть от нас низкоуровневые тонкости языка и предоставить инструменты и абстракции высокого уровня, которые могут быть использованы для написания асинхронного кода и управления конкурентностью, как мы могли бы сделать это в JavaScript и NodeJS.

Почему я должен использовать PHP, если у нас есть NodeJs и Go?

Подобный вопрос возникает чаще всего, когда речь идет об асинхронном РНР. Почему-то сообщество часто против использования PHP в качестве инструмента для написания асинхронного кода. Всегда кто-то предлагает просто использовать Go и NodeJs.

biglebowski

 

Этот твит assertchris прекрасно описывает это:

Basically how I feel about every "just use another language instead of async in PHP" post. pic.twitter.com/LnKXTIQodx

— assertchris (@assertchris) 8 декабря 2017 г.

Конечно, когда PHP был создан его целью было не быть языком программирования, который может быть использован для создания больших сложных приложений. Когда это произошло, не было никакого JavaScript и никакого асинхронного кода. Но сейчас PHP совершенно другой, который уже имеет некоторую встроенную функциональность для написания асинхронного кода (например, функцию stream_select()).

Да, вы можете использовать Go или NodeJs для создания асинхронных приложений, но это не всегда уместно. Когда у вас уже есть солидный опыт в PHP, для вас будет намного проще разобраться в некоторых библиотеках и инструментах, подходящих вам, вместо того, чтобы учить новый язык и новую экосистему. Такие инструменты, как ReactPHP или Amp позволяют писать асинхронный код, как вы пишете это в NodeJS. Эти инструменты являются довольно зрелыми и имеют стабильные версии, так что вы можете смело использовать их в продакшене.

Заключение

Не бойтесь изучать новые парадигмы языка. PHP — это гораздо больше, чем запустить скрипт, выполнить некоторый код и умереть. Вы будете удивлены, используя ваш знакомый и привычный PHP в совершенно новом ключе, в таком, в каком вы никогда не использовали его! Асинхронный код и событийно-ориентированное программирование позволят расширить ваши представления о PHP и о том, как этот язык может быть использован. Нет необходимости изучать новый язык для написания асинхронных приложений только потому, что кто-то винит PHP в том, что это неправильный инструмент для этого, или потому, что я всегда делал это так, и это невозможно улучшить. Просто попробуйте!

2018-02-15 оригинал

Последние посты

Комментарии

авторизуйтесь или зарегистрируйтесь, чтобы оставить комментарий