Должен ли я использовать протоколы IOCP или перекрывающиеся WSASend / Receive?

Я изучаю варианты асинхронного ввода-вывода сокетов в Windows. Очевидно, существует более одного варианта: я могу использовать WSASend ... с перекрывающейся структурой, обеспечивающей либо обратный вызов завершения, либо событие, либо я мог бы использовать IOCP и (новый) пул потоков. Из того, что я обычно читаю, рекомендуется последний вариант.

Однако мне непонятно, зачем мне использовать IOCP, если для моей цели достаточно процедуры завершения: сказать сокету, чтобы он отправил этот блок данных, и сообщить мне, если это сделано.

Я понимаю, что материал IOCP в сочетании с CreateThreadpoolIo и т. Д. Использует пул потоков ОС. Однако «нормальный» перекрывающийся ввод-вывод также должен использовать отдельные потоки? Так в чем разница / недостаток? Вызывается ли мой обратный вызов потоком ввода-вывода и блокирует ли другие вещи?

Заранее спасибо, Кристоф


person Christoph    schedule 02.08.2013    source источник


Ответы (2)


Вы можете использовать любой из них, но для серверов IOCP с «очередью завершения» будет иметь лучшую производительность в целом, потому что он может использовать несколько клиентских ‹> серверных потоков, либо с CreateThreadpoolIo, либо с некоторым пулом потоков пользовательского пространства. Очевидно, что в этом случае обычно используются выделенные потоки-обработчики.

Перекрывающийся ввод-вывод процедуры завершения более полезен для клиентов, ИМХО. Процедура завершения запускается асинхронным вызовом процедуры, который ставится в очередь потоку, инициировавшему запрос ввода-вывода (WSASend, WSARecv). Это означает, что этот поток должен быть в состоянии обработать APC, и обычно это означает цикл while (true) вокруг некоторого вызова blahEx (). Это может быть полезно, потому что довольно легко дождаться блокирующей очереди или другого межпоточного сигнала, который позволяет потоку получать данные для отправки, а процедура завершения всегда обрабатывается этим потоком. Этот механизм ввода-вывода оставляет параметр OVL 'hEvent' свободным для использования - идеально подходит для передачи указателя на объект буфера связи в процедуру завершения.

Следует избегать перекрывающегося ввода-вывода с использованием фактического синхронного события / семафора / чего угодно для перекрывающегося параметра hEvent.

person Martin James    schedule 02.08.2013
comment
Почему следует избегать подхода событий синхронизации? - person nitzanms; 22.06.2016

В документации Windows IOCP рекомендуется использовать не более одного потока на доступное ядро ​​на порт завершения. Гиперпоточность удваивает количество ядер. Поскольку использование IOCP приводит к созданию для всех практических целей приложения, управляемого событиями, использование пулов потоков добавляет в планировщик ненужную обработку.

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

Конечно, у вас могут быть пулы потоков для обработки, не связанной с IOCP.

ИЗМЕНИТЬ __ _ __ _ __ _ __ _ __ _ _

Сокет (файловые дескрипторы тоже работают нормально) связан с IOCP. Процедура завершения ожидает IOCP. Как только запрошенное чтение из сокета или запись в сокет завершает работу ОС - через IOCP - освобождает процедуру завершения, ожидающую IOCP, и возвращается с дополнительной информацией, которую вы предоставили, когда вы вызывали чтение или запись (я обычно передаю указатель на блок управления). Таким образом, процедура завершения немедленно «знает», где найти информацию, относящуюся к завершению.

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

Если вы пишете сервер, подключенный к Интернету, сервер будет выдавать чтение, чтобы дождаться ввода от клиента. Этот ввод может поступить через миллисекунду или неделю спустя, и когда это произойдет, IOCP выпустит процедуру завершения, которая анализирует ввод. Обычно он отвечает записью, содержащей данные, запрошенные на входе, а затем ожидает IOCP. Когда запись завершена, IOCP снова освобождает процедуру завершения, которая видит, что запись завершена, (обычно) выдает новое чтение и запускается новый цикл.

Таким образом, приложение на основе IOCP обычно потребляет очень мало (или совсем не потребляет) ЦП до момента завершения, когда процедура завершения идет полным ходом, пока не завершит обработку, отправит новый запрос ввода-вывода и снова ждет порта завершения. . За исключением тайм-аута IOCP (который может использоваться для сигнализации служебного или подобного) все связанные с вводом-выводом вещи происходят в ОС.

Чтобы еще больше усложнить (или упростить) вещи, нет необходимости, чтобы сокеты обслуживались с помощью подпрограмм WSA, функции Win32 ReadFile и WriteFile работают нормально.

person Olof Forshell    schedule 15.08.2013
comment
Все это предполагает, что работа ваших потоков никогда не блокируется (что было бы идеально, но не всегда возможно). Наличие дополнительных потоков, ожидающих IOCP, может помочь, если потоки CAN блокируются; профиль и посмотреть. Наличие собственного пула потоков может помочь, если вы хотите гарантировать, что ваш ввод-вывод обрабатывается раньше других потенциальных пользователей общего пула и т. Д. В идеале каждый объект «соединение» (ключ завершения) имеет свою собственную внутреннюю очередь, так что если вы получить несколько завершений (чтение и запись?) для одного и того же «соединения», вы не используете два своих потока IOCP и не заставляете их бороться за одно и то же «соединение» ... - person Len Holgate; 15.08.2013
comment
@Olof: Итак, вы все равно рекомендовали бы IOCP вместо перекрывающегося ввода-вывода, но я должен выделить один поток для порта завершения. Я правильно понял? И не ограничивает ли это количество дескрипторов, которые я должен связать с портом (используя параметр ExistingCompletionPort)? - person Christoph; 16.08.2013
comment
IOCP - это перекрывающиеся операции ввода-вывода, но они намного более эффективны, чем ожидание нескольких объектов (что, по сути, является формой опроса). У вас не должно быть больше потоков, ожидающих на IOCP, чем у вас есть ядер (ядро с гиперпоточностью считается как два) в системе. Поэтому, если у вас четыре ядра, у вас не должно быть более четырех потоков, ожидающих IOCP. Конечно, если у вас есть два IOCP, у вас не должно быть более четырех потоков в каждом (и, возможно, настроить приоритеты потоков, как у меня). - person Olof Forshell; 17.08.2013