Бележки на програмиста

Пример за най-простата многонишкова програма WinAPI

Продължаваме да припомняме Windows API. Досега сме писали тъжни и скучни еднонишкови програми от поредицата „извикайте правилната процедура с необходимите аргументи и разгледайте нейния код за връщане“. Днес най-накрая ще напишем, макар и проста, но все пак същата многонишкова програма, с едни и същи нишки и мютекси, които всички постоянно се карат.

От новите процедури се нуждаем от следното.

Както се досещате, CreateThread създава нова нишка. Аргументи отляво надясно - (1) нещо, за което не е необходимо да знаете сега, (2) размер на стека в байтове, закръглен до размер на страницата, ако е нула, тогава се взема размерът по подразбиране, (3) указател към процедурата, от която да започне нишката за изпълнение, (4) аргументът на процедурата, предадена от предишния аргумент, обикновено се предава указател към някаква структура, (5) флагове, например, можете да създадете спряна нишка (CREATE_SUSPENDED) и след това го стартирайте с помощта на ResumeThread, (6), където да напишете ThreadId на създадения поток. Ако е успешна, процедурата връща манипулатора на създадения поток. В случай на грешка се връща NULL и подробностите могат да бъдат намерени чрез GetLastError.

CreateMutex създава нов мютекс. Не е нужно да знаете за първия аргумент сега. Ако вторият аргумент е TRUE, създаденият мютекс веднага ще бъде заключен от текущата нишка, ако вторият аргумент е FALSE, се създава отключен мютекс. Третият аргумент е името на мютекса, ако искаме да създадем именован мютекс. Все още не ни трябват именовани мютекси, така че преминаваме NULL. Връщаните стойности са точно същите като CreateThread.

WaitForSingleObject изчаква обектът, чийто манипулатор е предаден като първия аргумент, да влезе в сигнализирано състояние. Освен това, в зависимост от вида на обекта, процедурата може да промени състоянието си. WaitForSingleObject може да се приложи към мютекси, семафори, нишки, процеси и други. Ако hHandle е манипулатор на мютекс, процедурата изчаква мутексът да се освободи и след това го заключва. Ако hHandle е манипулатор на поток, тогава процедурата просто чака да завърши. Вторият аргумент определя времето за изчакване в милисекунди. Можете да чакате вечно, като въведете специалната стойност INFINITE. Ако посочите нула, процедурата не преминава в режим на заспиване, а се връща незабавно.

В случай на грешка процедурата връща WAIT_FAILED и GetLastError ще ви помогне да разберете подробностите. При успех се връща WAIT_OBJECT_0, ако изчакахме обектът да премине в състояние на сигнала и WAIT_TIMEOUT, ако изтече времето за изчакване. Също така можем да получим WAIT_ABANDONED. Това се случва, ако нишката, която държи мютекса, е прекратена, без да я освободи. В този случай мютексът се заключва от текущата нишка, но целостта на данните, достъпът до които е бил ограничен от мютекса, по очевидни причини е под въпрос.

WaitForMultipleObjects работи като WaitForSingleObject, но за масив от обекти. Първият аргумент указва размера на масива, трябва да бъде строго по-голям от нула и да не надвишава MAXIMUM_WAIT_OBJECTS (което е точно 64). Вторият аргумент е указател към масив от манипулатори. Масивът може да съдържа манипулатори на обекти от различни типове, но многократното включване на един и същ манипулатор се счита за грешка. Ако третият аргумент е ИСТИНА, процедурата изчаква преминаването в състояние на сигнала на всички обекти, в противен случай - за някой от посочените обекти. Семантиката на последния аргумент е точно същата, както в случая на WaitForSingleObject.

Процедурата връща WAIT_FAILED в случай на грешка и WAIT_TIMEOUT в случай на изчакване. Ако процедурата върне стойност от WAIT_OBJECT_0 на WAIT_OBJECT_0 + dwCount - 1, тогава в случай на bWaitAll == TRUE всички обекти от масива преминаха в състояние на сигнала, а в случай на bWaitAll == FALSE обектът с връщане на индекса код минус WAIT_OBJECT_0 премина в състояние на сигнала. Ако процедурата върне стойност от WAIT_ABANDONED_0 на WAIT_ABANDONED_0 + dwCount - 1, тогава в случай на bWaitAll == TRUE всички очаквани обекти преминаха в състояние на сигнала и поне един от обектите се оказа хвърлен мютекс и в случай на bWaitAll == FALSE, мутекс с индекс беше хвърлен код за връщане минус WAIT_ABANDONED_0.

Ако bWaitAll == TRUE, процедурата не променя състоянието на обектите, докато не бъдат сигнализирани всички. По този начин, докато чакате, мютексите могат да продължат да се заключват и отключват от други нишки. Ако bWaitAll == FALSE и няколко обекта преминат в състояние на сигнала наведнъж, процедурата винаги работи с обекта в масива, който има минималния индекс.

ReleaseMutex ще отключи предварително заключен мютекс. При успех процедурата връща ненулева стойност. Нула се връща при грешка и подробностите са достъпни чрез GetLastError. За съжаление, за да се предотвратят блокировки, Windows позволява на нишките да заключват един и същ мутекс няколко пъти, но броят на извикванията на ReleaseMutex трябва да е подходящ.

Процедурата на заспиване блокира текущата нишка за посочения брой милисекунди. Ако предадете INFINITE като параметър, нишката ще бъде блокирана завинаги. Ако предадете нула, нишката отдава останалото време на процесора си на която и да е друга нишка със същия приоритет. Процедурата няма връщана стойност (VOID).

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

Сега нека да разгледаме цялата тази икономика в действие:

#define THREADS_NUMBER 10
#define ITERATIONS_NUMBER 100
#define PAUSE 10/* ms * /

DWORD dwCounter = 0;

DWORD WINAPI ThreadProc (CONST LPVOID lpParam) <
CONST HANDLE hMutex = (CONST HANDLE) lpParam;
DWORD i;
за (i = 0; i < ITERATIONS_NUMBER; i ++ ) <
WaitForSingleObject (hMutex, INFINITE);
dwCounter ++;
ReleseMutex (hMutex);
Сън (ПАУЗА);
>
ExitThread (0);
>

VOID Грешка (CONST HANDLE hStdOut, CONST LPCWSTR szMessage) <
DWORD dwTemp;
TCHAR szError [256];
WriteConsole (hStdOut, szMessage, lstrlen (szMessage), & dwTemp, NULL);
wsprintf (szError, TEXT ("LastError =% d \ r \ n"), GetLastError ());
WriteConsole (hStdOut, szError, lstrlen (szError), & dwTemp, NULL);
ExitProcess (0);
>

INT основно () <
TCHAR szMessage [256];
DWORD dwTemp, i;
РАБОТА hThreads [THREADS_NUMBER];
CONST HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);
CONST HANDLE hMutex = CreateMutex (NULL, FALSE, NULL);
ако (NULL == hMutex) <
Грешка (hStdOut, TEXT ("Неуспешно създаване на мутекс. \ R \ n"));
>

за (i = 0; i < THREADS_NUMBER; i ++ ) <
hThreads [i] = CreateThread (NULL, 0, & ThreadProc, hMutex, 0, NULL);
if (NULL == hThreads [i]) <
Грешка (hStdOut, TEXT ("Неуспешно създаване на нишка. \ R \ n"));
>
>

WaitForMultipleObjects (THREADS_NUMBER, hThreads, TRUE, INFINITE);
wsprintf (szMessage, TEXT ("Брояч =% d \ r \ n"), dwCounter);
WriteConsole (hStdOut, szMessage, lstrlen (szMessage), & dwTemp, NULL);

за (i = 0; i < THREADS_NUMBER; i ++ ) <
CloseHandle (hThreads [i]);
>
CloseHandle (hMutex);
ExitProcess (0);
>

Допълнение: Между другото, използвайки имена мутекси, можете да го направите така, че потребителят да може да изпълнява само един екземпляр на приложението. Тук можете да видите пример за това как се прави.