Зачем использовать асинхронные запросы вместо использования большего пула потоков?


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

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

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

но после просмотра этого у меня еще есть некоторые вопросы.

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

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

3 65

3 ответа:

Это очень хороший вопрос, и понимание этого является ключевым, чтобы понять, почему асинхронный ввод-вывод так важен. Причина, по которой новая функция async/await была добавлена в 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 МБ для стека. Для веб-страницы, создающей IO, которая длится 5 секунд, и с нагрузкой 500 запросов в секунду вам понадобится 2500 потоков в вашем пул потоков, что означает 2,5 ГБ памяти для стеков потоков, которые будут сидеть ничего не делая. Тогда у вас есть проблема переключения контекста, что будет иметь тяжелые последствия для производительности вашей машины (влияющие на все службы на машине, а не только ваше веб-приложение). Несмотря на то, что Windows довольно хорошо справляется с игнорированием ожидающих потоков, она не предназначена для обработки такого большого количества потоков. Помните, что наибольшая эффективность достигается при количестве запущенных потоков равно количеству логических процессоров на машине (обычно не более 16).

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

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

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

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

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

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

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

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

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