Зачем использовать асинхронные запросы вместо большого пула потоков? - PullRequest
66 голосов
/ 26 февраля 2012

Во время Techdays здесь, в Нидерландах, Стив Сандерсон выступил с докладом о C # 5, ASP.NET MVC 4 и асинхронной сети .

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

Затем он показал, как использование асинхронных веб-запросов повышает производительность, потому что работа затем делегируется другому потоку, и пул потоков может быстро отвечать на новые входящие запросы. Он даже продемонстрировал это и показал, что 50 одновременных запросов сначала занимали 50 * 1 с, но при асинхронном поведении всего 1,2 с.

Но, увидев это, у меня все еще есть вопросы.

  1. Почему мы не можем просто использовать больший пул потоков? Не используется ли async / await для запуска другого потока медленнее, чем просто увеличение пула потоков с самого начала? Это не похоже на то, что сервер, на котором мы работаем, вдруг получает больше потоков или что-то в этом роде?

  2. Запрос от пользователя все еще ожидает завершения асинхронного потока. Если поток из пула делает что-то еще, как поток «UI» остается занятым? Стив упомянул кое-что о «умном ядре, которое знает, когда что-то закончено». Как это работает?

Ответы [ 3 ]

63 голосов
/ 27 февраля 2012

Это очень хороший вопрос, и понимание его является ключом к пониманию того, почему асинхронный ввод-вывод так важен.Причина, по которой новая функция асинхронности / ожидания была добавлена ​​в C # 5.0, заключается в упрощении написания асинхронного кода.Поддержка асинхронной обработки на сервере не нова, однако она существует, поскольку ASP.NET 2.0.

Как показал Стив, при синхронной обработке каждый запрос в ASP.NET (и WCF) получает один поток изпул потоков.Проблема, которую он продемонстрировал, - это хорошо известная проблема под названием " истощение пула потоков ".Если вы выполняете синхронный ввод-вывод на вашем сервере, поток пула потоков останется заблокированным (ничего не делая) на время ввода-вывода.Поскольку существует ограничение на количество потоков в пуле потоков, под нагрузкой это может привести к ситуации, когда все потоки пула потоков блокируются в ожидании ввода-вывода, и запросы начинают помещаться в очередь, что приводит к увеличению времени отклика.Поскольку все потоки ожидают завершения ввода-вывода, вы увидите, что загрузка ЦП близка к 0% (даже несмотря на то, что время отклика идет вверх).

Что вы спрашиваете ( Почему может 'мы просто используем больший пул потоков? ) это очень хороший вопрос.Фактически, именно так большинство людей до сих пор решали проблему истощения пула потоков: просто добавьте больше потоков в пул потоков.Некоторая документация от Microsoft даже указывает, что в качестве исправления для ситуаций, когда может произойти голодание пула потоков.Это приемлемое решение, и до C # 5.0 это было гораздо проще сделать, чем переписать ваш код, чтобы он стал полностью асинхронным.

Однако есть несколько проблем с подходом:

  • Нет значения, которое работает во всех ситуациях : количество потоков пула потоков, которое вам понадобится, линейно зависит от продолжительности ввода-вывода и нагрузки на ваш сервер.К сожалению, задержка ввода-вывода в большинстве случаев непредсказуема.Вот пример: допустим, вы отправляете HTTP-запросы стороннему веб-сервису в приложении ASP.NET, выполнение которого занимает около 2 секунд.Вы сталкиваетесь с истощением пула потоков, поэтому вы решили увеличить размер пула потоков, скажем, до 200 потоков, и тогда он снова начнет работать нормально.Проблема в том, что, возможно, на следующей неделе у веб-службы возникнут технические проблемы, которые увеличат время отклика до 10 секунд.Внезапно истощение пула потоков вернулось, поскольку потоки блокируются в 5 раз дольше, поэтому теперь вам нужно увеличить число в 5 раз до 1000 потоков.

  • Масштабируемостьи производительность : Вторая проблема заключается в том, что если вы сделаете это, вы все равно будете использовать один поток на запрос.Нити являются дорогим ресурсом.Каждому управляемому потоку в .NET требуется выделение памяти для стека в 1 МБ.Для веб-страницы, выполняющей ввод-вывод, которая длится 5 секунд и при нагрузке 500 запросов в секунду, вам потребуется 2500 потоков в вашем пуле потоков, что означает 2,5 ГБ памяти для стеков потоков, которые будут бездействовать.Тогда у вас возникнет проблема переключения контекста, которая будет сильно влиять на производительность вашего компьютера (затрагивая все службы на компьютере, а не только ваше веб-приложение).Несмотря на то, что Windows неплохо справляется с игнорированием ожидающих потоков, она не предназначена для обработки такого большого количества потоков.Помните, что наибольшая эффективность достигается, когда количество запущенных потоков равно количеству логических процессоров на компьютере (обычно не более 16).

Таким образом, увеличение размера пула потоков - это решение, и люди занимаются этим уже десять лет (даже в собственных продуктах Microsoft), оно просто менее масштабируемо и эффективно с точки зрения использования памяти и ЦП, и вы всегда во власти внезапного увеличения латентности ввода-вывода, которое может вызвать голод. Вплоть до C # 5.0 сложность асинхронного кода не стоила проблем для многих людей. async / await меняет все, как сейчас, вы можете воспользоваться масштабируемостью асинхронного ввода-вывода и одновременно писать простой код.

Подробнее: http://msdn.microsoft.com/en-us/library/ff647787.aspx " Использование асинхронных вызовов для вызова веб-служб или удаленных объектов, когда есть возможность выполнить дополнительную параллельную обработку во время вызова веб-службы. По возможности избегайте синхронных (блокирование ) вызовы веб-служб, поскольку исходящие вызовы веб-служб выполняются с использованием потоков из пула потоков ASP.NET. Блокирующие вызовы уменьшают количество доступных потоков для обработки других входящих запросов."

31 голосов
/ 26 февраля 2012
  1. Async / await не основан на потоках; это основано на асинхронной обработке. Когда вы выполняете асинхронное ожидание в ASP.NET, поток запроса возвращается в пул потоков, поэтому потоки не обслуживают этот запрос до завершения асинхронной операции. Поскольку издержки запроса ниже, чем издержки потоков, это означает, что async / await может масштабироваться лучше, чем пул потоков.
  2. Запрос содержит число незавершенных асинхронных операций. Это количество управляется реализацией ASP.NET SynchronizationContext. Вы можете прочитать больше о SynchronizationContext в моей статье MSDN - она ​​рассказывает о том, как работает ASP.NET SynchronizationContext и как await использует SynchronizationContext.

Асинхронная обработка ASP.NET была возможна до асинхронного / ожидающего - вы можете использовать асинхронные страницы и использовать компоненты EAP, такие как WebClient (Асинхронное программирование на основе событий - это стиль асинхронного программирования, основанный на SynchronizationContext). Async / await также использует SynchronizationContext, но имеет синтаксис намного .

8 голосов
/ 11 августа 2014

Представьте себе пул потоков как набор работников, которых вы наняли для выполнения вашей работы.Ваши работники быстро бегут процессор инструкции для вашего кода.

Теперь ваша работа зависит от работы другого медленного парня;медленный парень - диск или сеть .Например, ваша работа может состоять из двух частей: одна часть должна выполнить до работы медленного парня, и одна часть должна выполнить после работы медленного парня.

Как бы вы посоветовали своим работникам выполнять вашу работу?Не могли бы вы сказать каждому работнику: «Сделайте эту первую часть, затем подождите, пока этот медлительный парень не закончит, а затем выполните свою вторую часть»?Вы бы увеличили количество своих работников, потому что все они, кажется, ждут этого медленного парня, и вы не в состоянии удовлетворить новых клиентов?Нет!

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

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

...