IO Completion Ports срещу Thread Pool API

Имах проблем, описан тук, и ми беше предложено да използвам портове за завършване на IO или пул от нишки.

Приложих IO завършване, извиквайки PostQueuedCompletionStatus за поставяне на задачата в опашка и GetQueuedCompletionStatus за получаване на следващата задача, за да я изпълни. Използвам порт за завършване на IO като безопасен за нишки FIFO контейнер за множество производители/много потребители без изрични заключвания. Това ме кара да имам пълен контрол върху нишките, защото може да се наложи да прекратя процеса ones за дълго време и да ги докладвам. Също така GetQueuedCompletionStatus чака извикващата нишка, ако няма останала задача.

Освен прекратяването, пулът от нишки отговаря на нуждите ми: задачите ми се изпълняват за по-малко от милисекунда, но има много от тях. Също така извикването на QueueUserWorkItem и оставянето на ОС да извършва синхронизациите и изпълнението е по-лесно.

Има ли разлики между двата подхода по отношение на ефективността? Някакви коментари относно моята реализация?


person ali_bahoo    schedule 22.12.2010    source източник
comment

Последният параметър тук е най-малкото грешен:

 pcre_exec(re, 0, str, strlen(str), 0, 0, ovector, sizeof(ovector))

sizeof(ovector) дава размера в байтове, но pcre_exec се нуждае от броя int, така че променете това на

pcre_exec(re, 0, str, strlen(str), 0, 0, ovector, sizeof(ovector)/sizeof(ovector[0]))

В документацията се посочва още

 ovecsize     Number of elements in the vector (a multiple of 3)

Намеква се, че размерът трябва да е кратен на 3, въпреки че тази част е доста неясна.

  -  person Chris Becke    schedule 22.12.2010
comment
@Chris Becke: Под набор от нишки имам предвид извикване на QueueUserWorkItem за поставяне на задачите в опашка.   -  person ali_bahoo    schedule 22.12.2010
comment
Прочетох въпроса, към който сте се свързали, и обмисляли ли сте Visual Studio 2010 Concurrency Runtime?   -  person Puppy    schedule 22.12.2010
comment
@DeadMG: Не Visual Studio 2010 Concurrency Runtime, а API на пула от нишки. Но ако няма голяма разлика, няма да променя изпълнението си. Вече е твърде сложно. И има проблем с прекратяването на нишката. Прекратяването не се предлага поради изтичане на стек така или иначе, но при обединяването на нишки може да има още по-големи проблеми. С io completion port вие сте свободни с нишките, защото вие ги управлявате.   -  person ali_bahoo    schedule 22.12.2010
comment
Не правите нищо с IO, така че защо изобщо да използвате IO Completion Port? Ако искате пълен контрол над нишките, тогава създайте свой собствен пул от нишки (използвайте WaitForMultipleObjects и т.н., за да изчакате и сигнализирате за задача).   -  person Zach Saw    schedule 22.12.2010
comment
@ZachSaw Целта на създаването на порт за завършване е, защото той е механизмът, използван за внедряване на пул от нишки. Пуловите нишки на Windows извикват GetQueuedCompletionStatus и блокиращо извикване, което ще се върне, когато има работа за вършене. Windows ще присвои работата на една от нишките, които не работят в GetQueuedCompletionStatus, за да им даде работата.   -  person Ian Boyd    schedule 27.10.2013


Отговори (4)


Портовете за завършване са проектирани да избягват ненужно превключване на контекста. Когато вашата нишка, която извиква GetQueuedCompletionStatus, приключи с обработката на работен елемент, тя може да се обади обратно на GetQueuedCompletionStatus, за да продължи да получава повече работа в рамките на текущия отрязък от време на процесора.

@Jonathan – Ако имате блокиращи обаждания, тогава те почти никога не трябва да се правят в нишка, изтегляща работни елементи. Те трябва или да се изпълняват асинхронно (с извикване Begin/End или *Async) или да се блокират в друга нишка (пул работни нишки). Това ще гарантира, че всички ваши нишки, обслужващи порта за завършване, действително вършат работа, вместо да губите време в блокиране, когато други работни елементи са налични.

Малко пояснение: ако управлявате свои собствени нишки и извиквате GetQueuedCompletionStatus, тогава сте създали свой собствен порт за завършване, отделен от порта за завършване на IO и свързания пул от нишки, използвани от операционната система за асинхронни IO извиквания.

person Karg    schedule 16.02.2011
comment
Да, в цикъл while нишка извиква GetQueuedCompletionStatus(), получава задачата и я изпълнява. Много е ефективен. Портът за завършване на IO почти работи с 5 нишки от 10 за милиони задачи с ~0% натоварване на процесора. Най-полезният API, който някога съм използвал. - person ali_bahoo; 16.02.2011

IO Completion Port (IOCP) обикновено се използва с пул от нишки за обработка на IO събития/дейности, докато пулът от нишки на WinAPI (който посочвате чрез QueueUserWorkItem) е просто изпълнение на Microsoft на типичен пул от нишки, който ще обработва задачи, различни от IO.

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

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

person Zach Saw    schedule 22.12.2010
comment
На първо място, предпочитам да разчитам на добре тестван, документиран, проектиран (надявам се :) ) специфичен за ОС API. Втората персонализирана реализация на нишки е много, много трудна. Освен това, с всички събития, семафори или дори критични секции, изричното заключване ще забави внедряването ви. По-малко ключалки, превключватели на нишки ще доведат до по-голяма ефективност IMHO. Всички тези API управляват тези неща. - person ali_bahoo; 22.12.2010
comment
@sad_man: Не е вярно. Пулът от нишки е изключително лесен. Създайте 10 нишки, изчакайте WaitForSingleObject и сигнализирайте задача с SetEVent. Това е пул от нишки. Пулът от нишки на WinAPI изисква да направите същата синхронизация и заключвания, ако вашият дизайн го изисква. Нито за секунда не си мислете, че операционната система се справя с тях вместо вас. - person Zach Saw; 22.12.2010
comment
@sad_man: Както казах в отговора си, ако имате нужда от пълен контрол, тогава единственият начин е да създадете свой собствен пул от нишки. Ако искате да разчитате на WinAPI threadpool, ще трябва да се примирите с неговия дизайн и внедряване. - person Zach Saw; 22.12.2010
comment
WaitForSingleObject и всички тези, които споменах, бяха WinAPI - те са толкова надеждни, колкото и WinAPI. - person Zach Saw; 23.12.2010

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

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

person Jonathan    schedule 23.12.2010
comment
Всъщност моите задачи не отнемат много време, но в моето приложение потребителите могат да пишат собствените си кодове и моето безпокойство е какво ще стане, ако кодът на потребителя влезе в безкраен цикъл или цикъл, който отнема много дълго време, за да излезе. Не мога да рискувам с другите работещи кодове. Така че донякъде трябва да принудя тази нишка да приключи. Можете ли да дадете някакъв материал за нишките, придобиващи CRITICAL_SECTION на купчина? - person ali_bahoo; 23.12.2010

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

Практическата разлика е - QueueUserWorkItem създава (и управлява) нишки в пула от нишки. Като такъв, той не знае предварително колко нишки ще са му необходими: Започва с няколко (може би само една) и след това създава допълнителни нишки на пула на интервали, ако пулът на нишките няма свободни нишки за обработка на елементи в опашка.

Това означава, че използването на QueueUserWorkItem може да доведе до значително забавяне на обработката на елементи, ако се добавят много елементи, докато пулът е малък, следователно поредиците от добавени елементи могат да доведат до увеличаване на пула, отколкото е необходимо.

person Chris Becke    schedule 22.12.2010