3

В чем разница между использованием портов ввода-вывода ввода-вывода, а только с использованием RegisterWaitForSingleObject, чтобы поток потоков нитей ожидал завершения ввода-вывода?Порты ввода-вывода ввода-вывода против RegisterWaitForSingleObject?

Один из них быстрее, и если да, то почему?

+0

«Быстрее» - это термин, относящийся к задаче. Что вы планируете делать (или фактически делаете)? Эти две технологии заметно отличаются друг от друга, поэтому знание того, что вы пытаетесь выполнить, будет значительным, в котором лучше подходит ваша проблема. – WhozCraig

+0

@WhozCraig: Я делаю кучу больших дисковых чтений и обработки данных по мере их поступления; узким местом является ввод-вывод, поэтому целью является достижение максимальной пропускной способности (данные с разных дисков будут считываться параллельно, в то время как данные с одного и того же диска будут считываться последовательно). Я чувствую, что оба они будут работать для моего дизайна, я просто не знаю, какой из них использовать. – Mehrdad

ответ

8

IOCP - это, как правило, самый быстродействующий механизм обхода IO, который вы найдете по одной причине, прежде всего: блокировка обнаружения.

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

  1. Пул N потоков для обслуживания запросов IOCP.
  2. Предел M потоков (M всегда < N) сообщает IOCP, сколько одновременных, незаблокированных потоков для разрешения.
  3. Цикл завершения состояния, в котором работают все потоки.

Разница между N и M в этом очень Важно. Общая философия заключается в том, чтобы настроить M на количество ядер на машине, а N - больше. Сколько больше зависит от количества времени, затрачиваемого вашими рабочими потоками в заблокированном состоянии. Если вы читаете дисковые файлы, ваши потоки будут привязаны к скорости канала ввода-вывода. Когда вы звоните по номеру ReadFile(), вы только что вводили блокирующий вызов. Если M == N, то, как только вы ударите все потоки, читая файлы на диске, вы полностью застопорились, со всеми потоками на канале IO на диске.

А что, если бы какой-то причудливый планировщик мог «знать», что этот поток (a) участвует в пуле потоков IOCP и (b) просто зашел в тупик, потому что он выдал вызов API, который будет занимать много времени ? Что, если, когда это произойдет, этот причудливый планировщик может временно «переместить» этот поток в специальную группу «running-but-stalled», а затем «освободить» дополнительный поток, который вызвался работать, пока потоки остановились?

То есть ровно, что приносит IOCP. Когда N равно больше, чем M, IOCP поставит поток, который только что выпустил стойло, в специальное состояние, но заторможенное, а затем временно «заимствует» дополнительный поток из вашего пула N. Он будет продолжать делать это до тех пор, пока пул N не будет исчерпан, или потоки, которые были остановлены, начинают возвращаться из своих запросов блокировки.

Итак, при этом свете IOCP, сконфигурированный так, чтобы иметь 8 потоков, одновременно работающих на 8-ядерной машине, может фактически иметь несколько сотен потоков в реальном пуле. Только 8 будет «разрешено» одновременно работать в незаблокированном состоянии, хотя вы можете появиться на нем временно, когда заблокированные потоки возвращаются из своих блоков, и вы уже заимствовали потоки, обслуживающие дополнительные запросы.

Наконец, хотя это и не так важно для вашей причины, все равно важно: нить IOCP не будет блокировать и не переключать контекст, если есть ожидание работы в очереди, когда она завершает свою текущую работу и выдает свой следующий вызов GetQueueCompletionStatus() , Если есть ожидание работы, оно поднимет его и продолжит выполнение без какого-либо обязательного упреждения. Конечно, планировщик ОС может в любом случае вытеснить, но только как часть общего планировщика; не из-за конкретного звонка на номер GetQueueCompletionStatus(). Единственное исключение из этого - если есть уже более M потоков, запущенных и не заблокированных.В этом случае GetQueueCompletionStatus() блокирует вызывающий поток до тех пор, пока он не понадобится снова для работы в режиме ожидания, когда достаточно потоков один раз - снова станет заблокированным.

Описание, которое вы указали, указывает на то, что вы будете сильно связаны с диском. Для абсолютных критически важных архитектур io-сервера практически невозможно обойти преимущества IOCP, , в частности обнаружение блока уровня ОС, которое позволяет планировщику знать, что он может временно освободить дополнительные потоки из вашего основного пула в накапливайте вещи, пока другие потоки застопориваются.

Вы просто не можете реплицировать эту особенность IOCP, используя пулы потоков Windows. Если бы все ваши потоки были числовыми скрещиваниями с небольшим или отсутствием ввода-вывода, я бы сказал, что пулы потоков лучше подходят, но ваша специфика диска-IO говорит мне, что вместо этого вы должны использовать IOCP.

+0

Удивительный ответ, спасибо! – Mehrdad

+2

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

+0

Я только что понял, что в документации 'QueueUserWorkItem' для' WT_EXECUTEDEFAULT' говорится, что «функция обратного вызова поставлена ​​в очередь на поток, который использует порты завершения ввода/вывода», тогда как документация RegisterWaitForSingleObject для одного и того же флага не включает это предложение. Знаете ли вы, что «RegisterWaitForSingleObject» использует IOCP для «WT_EXECUTEDEFAULT» или нет? Если это так, то для меня так же эффективно использовать «RegisterWaitForSingleObject»? (Согласно WINE, они оба используют 'RtlQueueWorkItem', поэтому, вероятно, они будут такими же ...) – Mehrdad

Смежные вопросы