#c# #multithreading #asynchronous #async-await
#c# #многопоточность #асинхронный #async-await
Вопрос:
Я знаю, как работает async await. Я знаю, что когда выполнение достигает await, оно освобождает поток и после завершения ввода-вывода извлекает поток из пула потоков и запускает оставшийся код. Таким образом, потоки эффективно используются. Но я запутался в некоторых случаях использования:
- Должны ли мы использовать асинхронные методы для очень быстрого метода ввода-вывода, например, для чтения / записи в кэше? Не приведут ли они к ненужному переключению контекста. Если мы используем метод синхронизации, выполнение завершится в том же потоке, и переключение контекста может не произойти.
- Async-await экономит только потребление памяти (создавая меньшие потоки). Или это также экономит процессор? Насколько я знаю, в случае синхронизации ввода-вывода во время выполнения ввода-вывода поток переходит в спящий режим. Это означает, что он не потребляет процессор. Правильно ли это понимание?
Комментарии:
1. async await не обязательно постоянно создает новые потоки. Вокруг этого много вопросов, поэтому вам нужно начать поиск.
2. Таким образом, он извлекает поток из пула потоков для выполнения оставшейся части метода. Это то, о чем я упоминал
3. также async await больше касается цепочки операций и позволяет платформе управлять переключением контекста, если это вообще может потребоваться.
4. @Pragmatic Это даже не обязательно использует пул потоков. Для задач, связанных с вводом-выводом, использование отдельных потоков только для ожидания результата может не дать существенного преимущества в производительности.
5. @Pragmatic Только в том случае, если контекста синхронизации нет, контекст синхронизации использует пул потоков или
ConfigureAwait(false)
используется. Конечно, это часто встречается во многих приложениях, но, конечно, не всегда .
Ответ №1:
Я знаю, как работает async await.
Это не так.
Я знаю, что когда выполнение достигает ожидания, оно освобождает поток
Это не так. Когда выполнение достигает ожидания, вычисляется ожидаемый операнд, а затем проверяется, завершена ли операция. Если это не так, то оставшаяся часть метода регистрируется как продолжение ожидаемого, и вызывающей стороне возвращается задача, представляющая работу текущего метода.
Ничто из этого не «освобождает поток». Скорее, управление возвращается вызывающему, и вызывающий продолжает выполняться в текущем потоке. Конечно, если текущий вызывающий был единственным в этом потоке, то поток завершен. Но нет требования, чтобы метод async был единственным вызовом в потоке!
после завершения ввода-вывода
Ожидаемая операция не обязательно должна быть операцией ввода-вывода, но давайте предположим, что это так.
он извлекает поток из пула потоков и запускает оставшийся код.
Нет. Он планирует выполнение оставшегося кода в правильном контексте. Этот контекст может быть потоком threadpool. Это может быть поток пользовательского интерфейса. Это может быть текущий поток. Это может быть любое количество вещей.
Должны ли мы использовать асинхронные методы для очень быстрого метода ввода-вывода, например, для чтения / записи в кэше?
Вычисляется ожидаемый. Если ожидающий знает, что он может завершить операцию за разумный промежуток времени, тогда он имеет полное право выполнить операцию и вернуть завершенную задачу. В этом случае штрафа нет; вы просто проверяете логическое значение, чтобы увидеть, выполнена ли задача.
Не приведут ли они к ненужному переключению контекста.
Не обязательно.
Если мы используем метод синхронизации, выполнение завершится в том же потоке, и переключение контекста может не произойти.
Я не понимаю, почему вы думаете, что переключение контекста происходит при операции ввода-вывода. Операции ввода-вывода выполняются на оборудовании, ниже уровня потоков ОС. Там нет потока, обслуживающего задачи ввода-вывода.
Экономит ли Async-await только потребление памяти (путем создания меньших потоков)
Цель await состоит в том, чтобы (1) более эффективно использовать дорогостоящие рабочие потоки, позволяя рабочим процессам становиться более асинхронными и тем самым освобождая потоки для выполнения работы в ожидании результатов с высокой задержкой, и (2) сделать исходный код для асинхронных рабочих процессов похожим на исходный код для синхронных рабочих процессов.
Насколько я знаю, в случае синхронизации ввода-вывода во время выполнения ввода-вывода поток переходит в спящий режим. Это означает, что он не потребляет процессор. Правильно ли это понимание?
Конечно, но у вас это полностью наоборот. ВЫ ХОТИТЕ ИСПОЛЬЗОВАТЬ ПРОЦЕССОР. Вы хотите постоянно потреблять как можно больше процессора! Процессор выполняет работу от имени пользователя, и если он простаивает, то его работа выполняется не так быстро, как могла бы. Не нанимайте работника, а затем платите ему за сон! Наймите работника, и как только он будет заблокирован в задаче с высокой задержкой, заставьте его работать над чем-то другим, чтобы процессор оставался как можно более горячим все время. Владелец этой машины заплатил хорошие деньги за этот процессор; он должен работать на 100% все время, пока есть работа, которую нужно выполнить!
Итак, давайте вернемся к вашему фундаментальному вопросу:
Увеличивает ли async await переключение контекста
Я знаю отличный способ узнать. Напишите программу, использующую await, напишите другую без нее, запустите их оба и измерьте количество переключений контекста в секунду. Тогда вы узнаете.
Но я не понимаю, почему переключение контекста в секунду является релевантной метрикой. Давайте рассмотрим два банка с большим количеством клиентов и большим количеством сотрудников. В банке # 1 сотрудники работают над одной задачей до ее завершения; они никогда не переключают контекст. Если сотрудник заблокирован в ожидании результата от другого, он переходит в спящий режим. В банке № 2 сотрудники переключаются с одной задачи на другую, когда они заблокированы, и постоянно обслуживают запросы клиентов. Как вы думаете, в каком банке обслуживание клиентов быстрее?
Комментарии:
1. Спасибо, Эрик, за ваш ответ! Таким образом, мы можем сказать, что мы всегда должны использовать aync API (если он доступен), независимо от того, выполняется ли он очень быстро или требует времени. Мне нужно разъяснение по одному вопросу: если одновременно запущено 100 потоков (на 4-ядерном компьютере), процессор переключится с одного потока на другой, если поток один начнет ввод-вывод. Или это основано исключительно на разделении времени, и все ресурсы получают равную долю ЦП (независимо от того, привязаны ли они к ЦП или к IO). Предполагая, что в этом случае используется API ввода-вывода синхронизации.
2. @usr: «просто измерьте это» — это действительно ответ и, безусловно, самый полезный ответ. Если у вас есть несколько лошадей, и вы хотите узнать, какая из них быстрее, спрашивать кучу незнакомых людей в Интернете, которые никогда не видели лошадей, гораздо менее полезно, чем участвовать в скачках.
3. @usr: И если действительно невозможно измерить соответствующие сценарии , то, по вашему предположению, на вопрос невозможно ответить правильно. Предположим, я сказал: «Ну, производительность улучшается во всех ситуациях, но в большинстве из них вы не можете измерить ее, чтобы увидеть, действительно ли производительность улучшилась». Имеет ли это какой-либо смысл? Является ли улучшение производительности, которое невозможно измерить, на самом деле улучшением? Как вы могли бы отличить это от регрессии?
4. @usr: единственные общие утверждения, которые я делаю в отношении производительности, — это (1) оставление потоков простаивающими, когда они могли бы выполнять работу, снижает производительность; «await» предназначен для предоставления инструмента для смягчения этой проблемы, и (2) достижение высокой производительности — это инженерная дисциплина, требующая эмпирических наблюдений за реальнымипрограммы выполняются в контролируемых условиях.
5. Теперь все эти вещи относительно незначительны, но в некоторых ситуациях они складываются. Команда Midori не перепроектировала большие части инфраструктуры c # async из-за синдрома not invented here, а потому, что в этом была реальная необходимость.
Ответ №2:
Должны ли мы использовать асинхронные методы для очень быстрого метода ввода-вывода, например, для чтения / записи в кэше?
Такой ввод-вывод не будет блокироваться в классическом смысле. «Блокировка» — это неопределенный термин. Обычно это означает, что процессор должен ждать аппаратного обеспечения.
Этот тип ввода-вывода выполняется исключительно процессором, и переключений контекста нет. Обычно это происходит, если приложение считывает файл или сокет медленнее, чем могут быть предоставлены данные. Здесь асинхронный ввод-вывод вообще не повышает производительность. Я даже не уверен, что было бы целесообразно разблокировать поток пользовательского интерфейса, поскольку все задачи могут выполняться синхронно.
Или это также экономит процессор?
Обычно это увеличивает загрузку процессора при реальных нагрузках. Это связано с тем, что асинхронный механизм добавляет обработку, распределение и синхронизацию. Кроме того, нам нужно перейти в режим ядра два раза вместо одного раза (сначала для инициирования ввода-вывода, затем для удаления из очереди уведомления о завершении ввода-вывода).
Типичные рабочие нагрузки выполняются с <<100% CPU. Производственный сервер с> 60% ЦП будет меня беспокоить, поскольку нет права на ошибку. В таких случаях рабочие очереди пула потоков почти всегда пусты. Таким образом, нет экономии при переключении контекста, вызванной обработкой нескольких завершений ввода-вывода при одном переключении контекста.
Вот почему загрузка ЦП обычно увеличивается (незначительно), за исключением случаев, когда на компьютере очень высокая загрузка ЦП, а рабочие очереди часто способны немедленно доставить новый элемент.
На сервере асинхронный ввод-вывод в основном полезен для сохранения потоков. Если у вас достаточно доступных потоков, вы получите нулевой или отрицательный выигрыш. В частности, любой отдельный ввод-вывод не станет на один бит быстрее.
Это означает, что он не потребляет процессор.
Было бы пустой тратой времени оставлять процессор недоступным во время выполнения ввода-вывода. Для ядра ввод-вывод — это просто структура данных. Пока он выполняется, работа процессора не выполняется.
Анонимный человек сказал:
Для задач, связанных с вводом-выводом, использование отдельных потоков только для ожидания результата может не дать существенного преимущества в производительности.
Передача одной и той же работы в другой поток, безусловно, не помогает с пропускной способностью. Это дополнительная работа, а не сокращенная работа. Это игра в оболочку. (И асинхронный ввод-вывод не использует поток во время его выполнения, поэтому все это основано на ложном предположении.)
Простой способ убедить себя в том, что асинхронный ввод-вывод обычно стоит дороже процессора, чем синхронизация ввода-вывода, — это запустить простой тест синхронизации и асинхронности TCP ping / pong. Синхронизация выполняется быстрее. Это своего рода искусственная нагрузка, поэтому это всего лишь намек на то, что происходит, а не всеобъемлющее измерение.
Комментарии:
1. Спасибо usr за ответ. Что вы подразумеваете под «Следовательно, нет экономии при переключении контекста, вызванной обработкой нескольких завершений ввода-вывода при одном переключении контекста»
2. Переключение контекста может быть сохранено, если несколько iOS инициируются без блокировки, а затем несколько iOS завершаются без блокировки. Тогда вам потребуется всего 2 переключения контекста для многих iOS. Это то, что экономит переключение. Но если iOS нечасто, что, как я утверждаю, происходит довольно часто, тогда невозможно объединить работу по инициализации и завершению в единовременный квант. Код должен будет блокироваться и отключаться, пока не поступит новая работа.
3. Спасибо, это имеет смысл!