Professional Documents
Culture Documents
Лабораторна робота № 3
Лабораторна робота № 3
Синхронізація потоків
Критичні секції
Критична секція (critical section) – це ділянка коду, яка повинна
використовуватися лише одним потоком одночасно. Якщо в один час два
або більше потоків намагаються здійснити доступ до критичної секції,
контроль над нею буде наданий лише одному з потоків, а всі інші будуть
блоковані (переведені в режим чекання) до тих пір, поки секція не буде
вільна.
Порівняно з іншими методами синхронізації (які будуть описані нижче)
створення критичної секції є вигідним з точки зору витрат
обчислювальних ресурсів. Проте на відміну від інших примітивів
синхронізації Windows його можна застосовувати лише в межах одного
процесу.
Критична секція вводиться змінною CRITICAL_SECTION. Цю змінну слід
ініціалізувати до вживання, до того ж вона повинна знаходитися в області
видимості для кожного потоку, що використовує її. Змінна критичної
секції не може виходити за межі зони видимості при використанні, тому
такі змінні частенько оголошуються як глобальні.
Для ініціалізації змінної CRITICAL_SECTION використовується функція
InitializeCriticalSection:
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
Wait-функції
М'ютекси
Об'єкти ядра "м'ютекси" гарантують потокам взаємовиключний доступ до
єдиного ресурсу. Звідси і виникла назва цих об'єктів (mutual exclusion,
mutex). Вони містять лічильник числа користувачів, лічильник рекурсії і
змінну, в якій запам'ятовується ідентифікатор потоку. М'ютекси
поводяться точно так, як і критичні секції. Проте, якщо останні є об'єктами
режиму користувача, то м'ютекси — об'єктами ядра. Крім того, єдиний
объект- м'ютекс дозволяє синхронізувати доступ до ресурсу декількох
потоків з різних процесів; при цьому можна задати максимальний час
чекання доступу до ресурсу.
Ідентифікатор потоку визначає, який поток захопив мьютекс, а лічильник
рекурсій — скільки разів.
Для використання об'єкта-м'ютекса один з процесів повинен спочатку
створити його викликом CreateMutex:
HANDLE CreateMutex(
PSECURITY_ATTRIBUTES psa,
BOOL bInitialOwner,
PCTSTR pszName);
Параметр bInitialOwner визначає початковий стан м'ютекса. Якщо в ньому
передається FALSE (що зазвичай і буває), об'єкт-м'ютекс не належить
жодному з потоків і тому знаходиться у вільному стані. При цьому його
ідентифікатор потоку і лічильник рекурсії дорівнюють 0. Якщо ж в ньому
передається TRUE, ідентифікатор потоку, якому належить мьютекс,
прирівнюється ідентифікатору потоку, що викликає, а лічильник рекурсії
набуває значення 1. Оскільки тепер ідентифікатор потоку відмінний від 0,
м'ютекс спочатку знаходиться в зайнятому стані.
Будь-який процес може отримати свій ("процесо-залежний") описувач
існуючого об'єкту "м'ютекс", викликавши OpenMutex:
HANDLE OpenMutex(
DWORD fdwAccess,
BOOL bInheritHandle,
PCTSTR pszName);
Поток дістає доступ до ресурсу, що розділяється, викликаючи одну з Wait-
функцій і передаючи їй описувач мьютекса, який охороняє цей ресурс.
Якщо Wait-функция визначає, що в м'ютекса ідентифікатор потоку не
дорівнює 0 (м'ютекс зайнятий), то потік, що викликає переходить в стан
чекання. Коли чекання м'ютекса потоком успішно завершується, останній
дістає монопольний доступ до захищеного ресурсу. Всі останні потоки, що
намагаються звернутися до цього ресурсу, переходять в стан чекання. Коли
поток, що займає ресурс, закінчує з ним працювати, він повинен звільнити
м'ютекс викликом функції ReleaseMutex:
BOOL ReleaseMutex(HANDLE hMutex);
Семафори
Об'єкти ядра "семафор" використовуються для обліку ресурсів. Як і всі
об'єкти ядра, вони містять лічильник числа користувачів, але, крім того,
підтримують два 32-бітові значення із знаком: одне визначає максимальне
число ресурсів (контрольоване семафором), інше використовується як
лічильник поточного числа ресурсів.
Для семафорів визначені наступні правила:
коли лічильник поточного числа ресурсів стає більше 0, семафор
переходить у вільний стан;
якщо цей лічильник дорівнює 0, семафор зайнятий;
система не допускає присвоєння негативних значень лічильнику
поточного числа ресурсів;
лічильник поточного числа ресурсів не може бути більше
максимального числа ресурсів.
Не плутайте лічильник поточного числа ресурсів з лічильником числа
користувачів об'єкту-семафора.
Об'єкт ядра "семафор" створюється викликом CreateSemaphore:
HANDLE CreateSemaphore(
PSECURITY_ATTRIBUTES psa,
LONG lInitialCount,
LONG lMaximumCount,
PCTSTR pszName);
Параметр lInitialCount визначає значення лічильника поточного числа
ресурсів, а параметр lMaximumCount – значення максимального числа
ресурсів.
Поток дістає доступ до ресурсу, викликаючи одну з Wait-функцій і
передаючи їй описувач семафора, який охороняє цей ресурс. Wait-функція
перевіряє в семафора лічильник поточного числа ресурсів, якщо його
значення більше 0 (семафор вільний), зменшує значення цього лічильника
на 1, і поток, що викликає, дістає доступ до ресурсу.
Поток збільшує значення лічильника поточного числа ресурсів,
викликаючи функцію ReleaseSemaphore:
BOOL ReleaseSemaphore(
HANDLE hSem,
LONG lReleaseCount,
PLONG plPreviousCount);
Події
Події - найпримітивніший різновид об'єктів ядра [3]. Вони містять
лічильник числа користувачів (як і всі об'єкти ядра) і дві булеві змінні:
одна повідомляє тип даного об'єкту-події, інша — його стан (вільний або
зайнятий).
Події просто повідомляють про закінчення якої-небудь операції. Об'єкти-
події бувають двох типів: із скиданням вручну (manual-reset events) і з
автоскиданням (auto-reset events). Перші дозволяють відновлювати
виконання відразу декількох потоків, що чекають, другі — лише одного.
Об'єкт ядра "подія" створюється функцією CreateEvent:
HANDLE CreateEvent(
PSECURITY_ATTRIBUTES psa,
BOOL bManualReset,
BOOL bInitialState,
PCTSTR pszName);
Параметр bManualReset (булева змінна) повідомляє систему, хочете Ви
створити подію із скиданням вручну (TRUE) або з автоскиданням (FALSE).
Параметру bInitialState визначає початковий стан події — вільний (TRUE)
або зайнятий (FALSE). Після того, як система створює об'єкт подію,
CreateEvent повертає описувач події, специфічний для конкретного
процесу. Потоки з інших процесів можуть дістати доступ до цього об'єкту:
1) викликом CreateEvent з тим же параметром pszName; 2) спадкоємством
описувача; 3) вживанням функції DuplicateHandle; і 4) викликом OpenEvent
з передачею в параметрі pszName імені, співпадаючого з вказаним в
аналогічному параметрі функції CreateEvent.
Непотрібний об'єкт ядра "подія" слід, як завжди, закрити викликом
CloseHandle. Створивши об'єкт подію, Ви можете безпосередньо управляти
його станом. Аби перевести його у вільний стан, Ви викликаєте:
BOOL SetEvent(HANDLE hEvent);
А аби поміняти його на зайняте
BOOL ResetEvent(HANDLE hEvent);
Для подій з автоскиданням діє наступне правило. Коли його чекання
потоком успішно завершується, цей об'єкт автоматично скидається в
зайнятий стан. Звідси і виникла назва таких об'єктів-подій. Для цього
об'єкту зазвичай не потрібно викликати ResetEvent, оскільки система сама
відновлює його стан. А для подій із скиданням вручну жодних побічних
ефектів успішного чекання не передбачено.
Для повноти картини згадаємо про ще одну функцію, яку можна
використовувати з об'єктами-подіями:
BOOL PulseEvent(HANDLE hEvent);
PulseEvent звільняє подію і тут же переводить її назад в зайнятий стан; її
виклик рівнозначний послідовному виклику SetEvent і ResetEvent. Якщо
Ви викликаєте PulseEvent для події із скиданням вручну, будь-які потоки,
що чекають цей об'єкт, стають планованими. При виклику цієї функції
стосовно події з автоскиданням прокидається лише один з потоків, що
чекають.
int main()
{
HANDLE hThread;
DWORD IDThread;
InitializeCriticalSection(&cs); // иніціалізуємо критичну секцію
hThread=CreateThread(NULL, 0, thread, NULL, 0, &IDThread);
if (hThread == NULL) return GetLastError();
for (int j = 10; j < 20; ++j)
{
EnterCriticalSection(&cs); // входимо у критичну секцію
for (int i = 0; i < 10; ++i)
{
cout << j << ' ' << flush;
Sleep(7);
}
cout << endl;
LeaveCriticalSection(&cs); // виходимо з критичної секції
}
WaitForSingleObject(hThread,INFINITE); // чекаємо, поки потік
// завершить свою роботу
DeleteCriticalSection(&cs); // закриваємо критичну секцію
return 0;
}
// відкриваємо м’ютекс
hMutex = OpenMutex(SYNCHRONIZE, FALSE, "DemoMutex");
if (hMutex == NULL) {
cout << "Open mutex failed." << endl;
cout << "Press any key to exit." << endl; cin.get();
return GetLastError();
}
int main()
{
HANDLE hThread;
DWORD IDThread;
cout << "An initial state of the array: ";
for (int i = 0; i < 10; i++) cout << a[i] <<' '; cout << endl;
// створюємо семафор
hSemaphore=CreateSemaphore(NULL, 0, 10, NULL);
if (hSemaphore == NULL) return GetLastError();
// створюємо поток, який готує елементи масиву
hThread = CreateThread(NULL, 0, thread, NULL, 0, &IDThread);
if (hThread == NULL) return GetLastError();
cout << "A final state of the array: "; // потік main виводить
for (int i = 0; i < 10; i++) // елементи масиву тільки
{ // після їх підготовки
WaitForSingleObject(hSemaphore, INFINITE); // потоком thread
cout << a[i] << " \a" << flush;
}
CloseHandle(hSemaphore);
CloseHandle(hThread);
return 0;
}
int main()
{
HANDLE hThread;
DWORD IDThread;
// створюємо потік
hThread = CreateThread(NULL,0,thread,NULL,0,&IDThread);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
CloseHandle(hOutEvent);
CloseHandle(hAddEvent);
Контрольні запитання