Задать вопрос

Асинхронная многопоточность в PHP: для чего?

Привет

Для чего она вообще нужна, объясните пожалуйста, знающие гуру. ;)

Интересует работа с сетью. Т.е. CURL, Socket, HTTP.
  • Вопрос задан
  • 4356 просмотров
Подписаться 5 Оценить Комментировать
Пригласить эксперта
Ответы на вопрос 2
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Все очень просто. Вот вам приблизительное значение таймингов доступа к данным:
io-cost.png

То есть запросив данные в сети мы тупо ждем. Долго ждем и ничего не делаем.

В случае с curl (он же HTTP) мы можем соорудить очередь запросов и послать их одним махом и ждать пока завершится загрузка всех документов в очереди для обработки результатов. Если мы хотим забрать 10 документов, то без multi curl у нас ушло бы времени "среднее время получения документа" * 10. И это примерно. В случае же с мультикурлом мы получаем время обработки 10 запросов как время выполнения самого долгого запроса. Если представить что время запросов всегда одинаковое, получаем выйгрыш примерно в 10 раз.

С сокетами веселее. Они бывают блокируемые (по умолчанию) и неблокируемые (выставляется опцией O_NONBLOCK). Для начала давайте определимся что такое чтение данных из сокетов и как нам это дело предоставляет операционная система. Упрощенно, когда мы создаем сокет, мы просто просим операционную систему предоставить оный. У каждого сокета есть буфер чтения и буфер записи. Если буфер записи полный - ОС начинает отправку данных пока буфер не опустеет (буфер записи нужен для организации проверки дошли ли пакеты и переотправки в случае чего, так же этот буфер замешан в выборе операционкой размеров пакетов и т.д. Это не особо важно в контексте вопроса). Когда данные приходят в сокет, сначала они помещаются в буфер чтения. Там они лежат пока их не попросят вернуть из кода. Так мы можем быть уверены в том, что данные не пропадут.

Так вот... возьмем блокирующие сокеты и попробуем запросить 1024 байт данных из оного. Причем клиент в данный момент ничего не отправляет, буфер чтения пустой. И так допустим минут 10. Как только мы сделали запрос за данными, и оказалось что буфер чтения пустой, процесс выполнения блокируется пока не появятся данные.

А теперь представим что проверять периодически наличие данных нам надо не в одном сокете а в десятке. Представим так же что 9 клиентов подключенных по нашим сокетам хорошие и присылают данные вовремя, а один не хороший и любит тупить по пол часа. Если бы мы пользовались блокирующими сокетами, то мы можем обрабатывать только одного клиента за раз. Причем если у него вдруг данных не оказалось - нам придется ждать, хотя в других сокетах уже вполне могли появиться данные какие для обработки. И если в случае с "хорошими" клиентами мы можем тратить на оных по пол секунды - секундочке, то наткнувшись на плохого клиента наш сервер замирает за те самые пол часа о которых мы договаривались. Сервер тупо ждет "плохого" клиента а хорошие в итоге не могут достучаться до сервера. Новых соединений мы так же не установим... короче все мертво.

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

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

Еще есть такая чудная штука, которую предоставляет операционная система как select или epol (в контексте php socket_select и stream_select). Данные функции позволяют нам скармливать массивы сокетов, за которыми вы следите (не сокетов, а их дескрипторов но не суть, и не один массив а три, массив дескрипторов что бы следить появились ли данные на чтение, записал ли сокет все и освободился ли буфер записи и третий отслеживает сокеты в которых произошли какие-то ошибки, например отвалилось соединение). Так же этой функции можно задавать таймаут, что очень удобно если мы сначала собираем данные с нескольких клиентов и если от них небыло вестей пару секунд, значит мы забрали все и можно начинать обработку.

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

Но все что выше имеет смысл только с TCP/TLS, если бы у нас были UDP сокеты, то было бы еще веселее. Там нету никаких буферов. Не принял данные - потерял данные. Нету соединений. Нету блокировок. Есть только пакеты. Поэтому этот протокол используют (или используют как основу) для реализации реалтайм систем. Задержек нету, а если какой пает не дошел, велика вероятность что он уже не актуален. Правда если сеть не надежная и потери пакетов велики, то начинается боль и слезы и обычно все же для таких случаев дублируют все на TCP.
Ответ написан
EnterSandman
@EnterSandman
Эникей
А для чего нужна многопоточность? Для параллельного выполнения какого-то действия.
Например парсинг чего-то.
Получить 100 страниц за сто секунд последовательно в 1 поток или 100 за 1 секунду в 100 потоков - есть разница?
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы