Поток ThreadPool выполняет операцию ввода-вывода - можно ли повторно использовать поток во время ожидания? - PullRequest
2 голосов
/ 03 июня 2019

Я немного читал об async / await против ThreadPool против потоков, и должен признать, что я не до конца понимаю детали. Есть один конкретный вопрос, на который я думаю у меня есть ответ, но я не могу точно сказать.

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

Что я собрал:

  • Использование async / await в операции ввода-вывода сделает этот поток доступным для других целей, в то время как операция ввода-вывода продолжается в другом месте
  • Для серверного приложения это означает более высокую пропускную способность и т. Д.

Однако коллега сказал что-то вроде этого:

  • что использование ThreadPool для выполнения той же операции ввода-вывода будет вести себя почти так же, как async / await
  • что, когда поток ThreadPool «передает» операцию ввода-вывода операционной системе, он может ждать ответа, но это не имеет большого значения, так как на процессоре не выполняется работа (поток в ожидании), и, следовательно, ThreadPool / OS / framework может использовать или порождать другой поток для выполнения какой-либо другой работы.
  • Поскольку пул потоков имеет ограничение в 32000+ потоков, это не имеет большого значения, поскольку он может просто использовать больше потоков. Стоимость потока очень мала, всего несколько байтов памяти

Итак, я спрашиваю:

  • Блокирует ли поток из ThreadPool операции ввода-вывода?
  • Имеет ли значение, если это так, поскольку OS / framework / Threadpool могут просто использовать другой поток из пула, если это необходимо для какой-либо другой работы?
  • Итог: операции ввода / вывода с async / await или ThreadPool; это имеет значение для эффективности и пропускной способности?

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

И извините заранее, если я пропустил точный ответ на этот вопрос, я посмотрел =)

1 Ответ

5 голосов
/ 03 июня 2019

Реальная проблема не в «пуле потоков против async / await», а в синхронном вводе-выводе и асинхронном вводе-выводе. [1]

В Windows потоки являются относительно дорогими объектами - с потоком связано много бухгалтерии ОС, не говоря уже о 1 МБ предварительно выделенного стекового пространства. Это накладывает довольно низкий предел на количество потоков, которые система будет поддерживать, и даже если вы не достигнете этого предела, переключение контекста между всеми этими потоками также не дешево. Это ограничение в 32 000 потоков является строго теоретическим и очень оптимистичным в отношении того, сколько потоков вы можете иметь и при этом быть отзывчивыми! [2]

Введите асинхронный ввод-вывод, который оптимизирован для использования только необходимого количества потоков (обычно это несколько консервативных кратных количеству ядер физических процессоров в системе), в идеале даже не создавая новых потоков за пределами первоначального пакета. Эти потоки предназначены для обработки завершенных операций ввода-вывода путем удаления их из очереди (известной как порт завершения). Пока выполняется асинхронная операция, никакой нити ей не выделено, даже как элемент в списке ожидания (у Стивена Клири есть хороший пост в блоге , в котором это объясняется более подробно). Не нужно много воображения, чтобы подумать о том, что более эффективно:

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

Как оказалось, последний масштабируется намного лучше, чем первый; модель "поток на запрос", которая является обычной в наивном серверном коде, быстро показывает свои пределы, даже когда вы используете пул потоков для сокращения создания новых потоков. Обратите внимание, что это было проблемой задолго до того, как async / await когда-либо был, а также решение, которое использовала Windows; async / await - это просто новый способ написания кода для использования существующих механизмов.

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


[1] Оказывается, Стивен Клири уже написал большую часть того, что в этом ответе несколько лет назад. Я также рекомендую вам прочитать это.

[2] Вот старый пост Марка Руссиновича, где он на самом деле пытается выжать как можно больше потоков из системы - просто для удовольствия и для получения прибыли. Он «только» добирается до 55K на 64-битной машине до того, как все ресурсы уйдут, и это с регулировкой размера стека по умолчанию и без какой-либо действительно полезной работы. В современной системе вы, вероятно, могли бы получить больше, но реальный вопрос не должен заключаться в том, «сколько потоков я могу иметь» - если это так, вы делаете это неправильно.

...