В чем разница между использованием портов завершения ввода-вывода и просто использованием RegisterWaitForSingleObject
заставить поток пула ожидать завершения ввода-вывода?
Является ли один из них быстрее, и если да, то почему?
В чем разница между использованием портов завершения ввода-вывода и просто использованием RegisterWaitForSingleObject
заставить поток пула ожидать завершения ввода-вывода?
Является ли один из них быстрее, и если да, то почему?
IOCP, как правило, являются самым быстродействующим механизмом обхода ввода-вывода, который вы найдете по одной причине, прежде всего: обнаружение блокировки.
Простым примером этого является сервер, который отвечает за обслуживание файлов с диска. IOCP обычно состоит из трех основных элементов:
Разница между N и M здесь очень важна. Общая философия заключается в том, чтобы настроить M как количество ядер на машине, а N — как большее число. Насколько больше, зависит от количества времени, которое ваши рабочие потоки проводят в заблокированном состоянии. Если вы читаете файлы с диска, ваши потоки будут привязаны к скорости канала дискового ввода-вывода. Когда вы делаете этот вызов ReadFile()
, вы только что ввели блокирующий вызов. Если M == N, то, как только вы нажмете на все потоки, читающие файлы с диска, вы полностью остановитесь, и все потоки будут на канале дискового ввода-вывода.
Но что, если бы у какого-нибудь хитроумного планировщика был способ «узнать», что этот поток (а) участвует в пуле потоков IOCP и (б) просто застопорился, потому что выдал вызов API, который займет много времени? Что, если, когда это произойдет, этот причудливый планировщик сможет временно «переместить» этот поток в специальную группу «работающих, но остановленных», а затем «освободить» лишний поток, который вызвался работать, пока есть остановленные потоки?
Это именно то, что предлагает IOCP. Когда N больше, чем M, IOCP переведет поток, который только что вызвал остановку, в специальное состояние «выполняется, но остановлен», а затем временно «заимствует» дополнительный поток из вашего пула N. Это будет продолжаться до тех пор, пока пул N не будет исчерпан или потоки, которые были остановлены, не начнут возвращаться из своих блокирующих запросов.
Таким образом, в этом свете IOCP, сконфигурированный так, чтобы, скажем, 8 потоков одновременно выполнялись на 8-ядерной машине, на самом деле может иметь несколько сотен потоков в реальном пуле. Только 8 из них когда-либо будут "разрешены" для одновременной работы в неблокированном состоянии, хотя вы можете временно отключить это, когда заблокированные потоки возвращаются из своих блоков, а у вас уже есть заимствованные потоки, обслуживающие дополнительные запросы.
Наконец, хотя это и не так важно для вашего дела, это все же важно: поток IOCP НЕ будет блокироваться и не будет переключаться контекст, если в очереди есть ожидающая работа, когда он завершает свою текущую работу и выдает следующий вызов GetQueueCompletionStatus()
. Если есть ожидающая работа, он подхватит ее и продолжит выполнение без обязательного вытеснения. Конечно, планировщик ОС в любом случае может выполнять вытеснение, но только как часть общего планировщика; не из-за конкретного вызова GetQueueCompletionStatus()
. Единственным исключением из этого является то, что уже запущено более M потоков и они не заблокированы. В этом случае GetQueueCompletionStatus()
будет блокировать вызывающий поток до тех пор, пока он снова не понадобится для бездействия, когда достаточное количество потоков снова будет заблокировано.
Описание, которое вы дали, указывает на то, что вы будете сильно привязаны к диску. Для абсолютно критичных к производительности архитектур серверов ввода-вывода практически невозможно превзойти преимущества IOCP, особенно обнаружение блоков на уровне ОС, которое позволяет планировщику знать, что он может временно освободить дополнительные потоки от ваш мастер-пул, чтобы поддерживать работу, пока другие потоки останавливаются.
Вы просто не можете воспроизвести эту специфическую функцию IOCP, используя пулы потоков Windows. Если бы все ваши потоки были обработчиками чисел с небольшим количеством операций ввода-вывода или вообще без них, я бы сказал, что пулы потоков подходят лучше, но ваша специфика дискового ввода-вывода подсказывает мне, что вместо этого вам следует использовать IOCP.
QueueUserWorkItem
для WT_EXECUTEDEFAULT
говорится, что функция обратного вызова ставится в очередь потока, который использует порты завершения ввода-вывода, тогда как документация RegisterWaitForSingleObject
для того же флага не включает это предложение. Вы знаете, использует ли RegisterWaitForSingleObject
IOCP для WT_EXECUTEDEFAULT
или нет? Если да, то будет ли мне так же эффективно использовать RegisterWaitForSingleObject
? (Согласно WINE, они оба используют RtlQueueWorkItem
, так что вполне вероятно, что они будут одинаковыми...)
- person user541686; 27.04.2013
RtlRegisterWait
и обнаружил, что он использует NtQueueApcThread
в потоке пула потоков вместо IOCP, по крайней мере, в Windows XP. :) Спасибо!
- person user541686; 27.04.2013