Порты завершения ввода-вывода и API пула потоков - PullRequest
3 голосов
/ 22 декабря 2010

У меня была проблема, описанная здесь , и мне предложили использовать порты завершения ввода-вывода или пул потоков.

Я реализовал завершение ввода-вывода, вызывая PostQueuedCompletionStatus, чтобы поставить задачу в очередь, и GetQueuedCompletionStatus, чтобы получить следующую задачу для ее выполнения. Я использую порт завершения ввода / вывода в качестве многопользовательского / многопользовательского поточно-ориентированного контейнера FIFO без явных блокировок. Это дает мне полный контроль над потоками, потому что мне, возможно, придется долго завершать процесс и сообщать о них. Также GetQueuedCompletionStatus ожидает вызывающий поток, если не осталось задач.

Помимо завершения, пул потоков удовлетворяет моим потребностям: мои задачи выполняются менее чем за миллисекунду, но их много. Также проще вызвать QueueUserWorkItem и позволить ОС выполнять синхронизацию и выполнение.

Есть ли различия между двумя подходами с точки зрения производительности? Любые комментарии о моей реализации?

Ответы [ 4 ]

3 голосов
/ 16 февраля 2011

Завершение Порты разработаны, чтобы избежать ненужного переключения контекста. Когда ваш поток, вызывающий GetQueuedCompletionStatus, завершает обработку рабочего элемента, он может сразу вернуться к GetQueuedCompletionStatus, чтобы продолжить получать больше работы в пределах своего текущего временного интервала ЦП.

@ Jonathan - Если у вас есть блокирующие вызовы, то эти никогда не должны выполняться в потоке, тянущем рабочие элементы. Они должны выполняться либо асинхронно (с вызовом Begin / End или * Async), либо блокироваться в другом потоке (пуле рабочих потоков). Это гарантирует, что все ваши потоки, обслуживающие порт завершения, фактически выполняют работу, а не тратят время на блокировку, когда доступны другие рабочие элементы.

Небольшое уточнение: если вы управляете своими собственными потоками и вызываете GetQueuedCompletionStatus, то вы создали свой собственный порт завершения, отдельный от порта завершения ввода-вывода и связанного пула потоков, используемых ОС для асинхронных вызовов ввода-вывода.

2 голосов
/ 22 декабря 2010

Порт завершения ввода-вывода (IOCP) обычно используется с пулом потоков для обработки событий / действий ввода-вывода, в то время как пул потоков WinAPI (который вы указываете через QueueUserWorkItem) является просто реализацией Microsoft типичного пула потоков, который будет обрабатывать задачи без ввода-вывода.

Глядя на вашу связанную ветку, кажется, что вы просто удаляете задачу из списка FIFO, который не имеет ничего общего с IO. Таким образом, последний, скорее всего, то, что вы после. Я не думаю, что разница в производительности должна быть вашей проблемой, в отличие от того, какой API вам подходит.

РЕДАКТИРОВАТЬ: если вам нужен полный контроль над созданием и завершением потока (хотя завершать поток не всегда нормально, так как стек не раскручивается), то вам лучше будет создать собственный пул потоков с помощью WaitForSingleObject (или, скорее, MultipleObjects) для сигнала выхода) и SetEvent. Пул потоков WinAPI - это, в основном, автоматическое создание и завершение потоков Microsoft в зависимости от загрузки потоков.

1 голос
/ 23 декабря 2010

Если вы используете порты завершения ввода-вывода и создаете свои собственные потоки X, которые вызывают GetQueuedCompletionStatus (), и у вас есть задачи X, которые занимают много времени (скажем, чтение из сети), тогда все потоки будут заняты, и дальнейшие запросы будут голодать. AFAIU, пул потоков будет вращать другой поток в этом случае.

Кроме того, никогда не используйте TerminateThread ()! При выделении памяти из кучи поток временно получает CRITICAL_SECTION этой кучи. Таким образом, если поток завершается в середине этого, другие потоки, пытающиеся выделить из той же кучи, будут зависать. И у вас нет возможности узнать, выделяет ли поток поток или нет.

0 голосов
/ 22 декабря 2010

Разница между QueueUserWorkItem и IOCompletionPorts в том, что QueueUserWorkItem - это простая в использовании абстракция более высокого уровня. Легко увидеть, как QueueUserWorkItem может быть реализован поверх IOCompletionPorts (я не знаю, так ли это).

Практическое отличие состоит в том, что QueueUserWorkItem создает (и управляет) потоки в пуле потоков. Таким образом, он не знает заранее, сколько потоков ему понадобится: он начинается с нескольких (возможно, только с одного), а затем создает дополнительные потоки пула с интервалами, если у пула потоков нет свободных потоков для обработки элементов в очереди.

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

...