Смекни!
smekni.com

Особливості багатозадачності в середовищі Windows (стр. 2 из 4)

83348630 83348630 83348630 83348629 83348629

Приклад. Допустимо, в програмі використовується ресурс, наприклад, файл або буфер в пам‘яті. Функція WriteToBuffer() викликається з різних потоків. Щоб уникнути колізій при одночасному зверненні до буферу з різних потоків, використовуємо м‘ютекс. Перед тим як звернутися до буфера, очікуємо „звільнення” м‘ютекса.

HANDLE hMutex;

int main()

{

hMutex = CreateMutex(NULL, FALSE, NULL); // Создаем мьютекс в свободном состоянии

//...

// Создание потоков, и т.д.

//...

}

BOOL WriteToBuffer()

{ DWORD dwWaitResult;

// Ждем освобождения мьютекса перед тем как обратиться к буферу.

dwWaitResult = WaitForSingleObject(hMutex, 5000L); // 5 секунд на таймаут

if (dwWaitResult == WAIT_TIMEOUT) // Таймаут. Мьютекс за єто время не освободился.

{

return FALSE;

}

else // Мьютекс освободился, и наш поток его занял. Можно работать.

{

Write_to_the_buffer().

...

ReleaseMutex(hMutex); // Освобождаем мьютекс.

}

return TRUE;

Семафор

Ще один вид синхронізаторів - семафор, що виключає. Основна його відмінність від критичної секції полягає в тім, що останню можна використовувати тільки в межах одного процесу (одного запущеного додатка), а семафорами, що виключають, можуть користатися різні процеси.

Semaphore – глобальний об’єкт синхронізації, що має лічильник для ресурсів, з ним пов‘язаних. В достатньо грубому приближенні м‘ютекс можна розглядати, як частковий випадок семафора з двома станами.

Іншими словами, критичні розділи - це локальні семафори, що доступні в рамках тільки однієї програми, а семафори, що виключають, можуть бути глобальними об'єктами, що дозволяють синхронізувати роботу програм (тобто різні запущені додатки можуть розділяти ті самі дані).

Розглянемо основні функції семафора, що виключає, на прикладі роботи з об'єктами mutex.

1. Створення об'єкта mutex

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes,

BOOL bInitialOwner,

LPCTSTR lpName)

lpMutexAttributes - покажчик на структуру SECURITY_ATTRIBUTES (у Windows 95 даний параметр ігнорується);

bInitialOwner - указує первісний стан створеного об'єкта (TRUE - об'єкт відразу стає зайнятим, FALSE - об'єкт вільний);

lpName - указує на рядок, що містить ім'я об'єкта. Ім'я необхідне для доступу до об'єкта інших процесів, у цьому випадку об'єкт стає глобальним і їм можуть оперувати різні програми. Якщо вам не потрібний іменований об'єкт, то вкажіть NULL. Функція повертає покажчик на об'єкт mutex. Надалі цей покажчик використовується для керування семафором, що виключає.

2. Закриття (знищення) об'єкта mutex

BOOL CloseHandle(HANDLE hObject)

3. Універсальна функція запиту доступу

DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) - універсальна функція, призначена для запиту доступу до синхронізуючого об'єкту (у даному випадку до об'єкта mutex).

hHandle - покажчик на синхронізуючий об'єкт (у даному випадку передається значення, повернуте функцією CreateMutex);

dwMilliseconds - час (у міллісекундах), протягом якого відбувається чекання звільнення об'єкта mutex. Якщо передати значення INFINITE (нескінченність), то функція буде чекати нескінченно довго.

Дана функція може повертати наступні значення:

WAIT_OBJECT_0 - об'єкт звільнився;

WAIT_TIMEOUT - час чекання звільнення пройшов, а об'єкт не звільнився;

WAIT_ABANDON - відбулося відмовлення від об'єкта (тобто процес, що володіє даним об'єктом, завершилося, не звільнивши об'єкт). У цьому випадку система (а не "процес-власник") переводить об'єкт у вільний стан. Таке звільнення об'єкта не припускає гарантій у захищеності даних;

WAIT_FAILED - відбулася помилка.

4. Звільнення об'єкта mutex

BOOL ReleaseMutex(HANDLE hMutex) - звільняє об'єкт mutex, переводячи його з зайнятого у вільний стан.

Чи дійде черга до вас?

Отже, якщо програмі необхідно ввійти в поділюваний код, то вона запитує дозвіл шляхом виклику функції WaitForSingleObject. При цьому якщо об'єкт синхронізації зайнятий, то виконання запитуючого потоку припиняється і невикористана частина відведеного часу передається іншому потоку. А тепер увага! Теоретично: як тільки об'єкт стає вільним, що очікує потік відразу захоплює його. Але це тільки теоретично. На практиці цього не відбувається. Захоплення об'єкта, що звільнився, відбувається лише тоді, що коли очікує потік знову одержить свій квант часу. І тільки тоді він зможе перевірити, чи звільнився об'єкт, і, якщо так, захопити його.

Непрямим підтвердженням вищевикладених міркувань може служити той факт, що Microsoft не передбачила підтримку черговості запитів на доступ до об'єкта синхронізації. Тобто якщо кілька процесів очікують звільнення того самого об'єкта синхронізації, то немає ніякої можливості довідатися, який саме з них першим одержить доступ до об'єкта, що звільнився.

Пояснимо це на наступному прикладі. Нехай, трьом потокам необхідно звернутися до однієї ділянки коду, причому одноразово ця ділянка повинна виконувати тільки один потік. Введемо об'єкт синхронізації mutex, що регулює доступ потоків до цієї ділянки коду. Коли потік 1 захопив об'єкт mutex і став виконувати поділювану ділянку коду, потік 2 запросив дозвіл на доступ (тобто викликав функцію WaitForSingleObject), а система перевела потік 2 у режим чекання. Через якийсь час потік 3 теж запросив дозвіл на вхід у цей код і теж перейшов у режим чекання. Тепер, якщо потік 1 звільнить об'єкт синхронізації, те невідомо, який потік (2 чи 3) його захопить, - усі залежить від того, хто з них першим одержить свій квант часу для продовження роботи. Нехай об'єктом синхронізації заволодів потік 3, а поки він виконував поділюваний розділ, потік 1 знову запросив доступ до об'єкта синхронізації - і знову стало два конкуруючих потоки (1 і 2). І хто з них першим "достукається" до ділянки коду, що виконується, невідомо: може случитися так, що потік 2 ніколи не буде допущений до бажаної ділянки коду і надовго залишиться в стані чекання... А як відомо, гірше немає чекати, хоча потоку це байдуже. Інша справа - вам...

Події

Подія - це об‘єкт синхронізації, стан якого може бути установлений сигнальним шляхом виклику функцій SetEvent або PulseEvent. Існує два типа подій:

Тип об‘єкту Опис
Подія з ручним “сбросом” Це об‘єкт, сигнальний стан якого зберігається до ручного” сброса” функцією ResetEvent. Як тільки стан об‘єкту установлений в сигнальний, всі потоки, що знаходяться в циклі очікування цього об‘єкту, продовжують своє виконання (звільняються).
Подія з автоматичним “сбросом” Об‘єкт, сигнальний стан якого зберігається до тих пір, поки не буде звільнений єдиний потік, після чого система автоматично установлює несигнальний стан події. Якщо нема потоків, очікуючих цієї події, об‘єкт залишається в сигнальному стані.

Події корисні в тих випадках, коли необхідно послати повідомлення потоку, яке сповіщає, що відбулася певна подія. Наприклад, при асинхронних операціях вводу и виводу з одного пристрою, система установлює подію в сигнальний стан коли закінчується якась із цих операцій. Один потік може використовувати декілька різних подій в декількох операціях, що перекриваються, а потім очікувати приходу сигналу від любого з них.

Потік може використовувати функцію CreateEvent для створення об‘єкту подія. Створюючий подію потік встановлює її початковий стан. В цьому потоці можна вказати ім‘я події. Потоки інших процесів можуть отримати доступ до цієї події по імені, вказав його у функції OpenEvent.

Потік може використовувати функцію PulseEvent для установки стану події - сигнальним і потім “сбросить” стан в несигнальне значення після звільнення відповідної кількості очікуючих потоків. У випадку об‘єктів з ручним “сбросом” звільняються всі очікуючі потоки. У випадку об‘єктів з автоматичним “сбросом” звільняється тільки єдиний потік, навіть якщо цієї події очікують декілька потоків. Якщо очікуючих потоків нема, PulseEvent просто встановлює стан подій - несигнальний.

#include <windows.h>

#include <process.h>

#include <stdio.h>

HANDLE hEvent1, hEvent2;

int a[ 5 ];

void Thread(void* pParams)

{

int i, num = 0;

while (TRUE)

{

WaitForSingleObject(hEvent2, INFINITE);

for (i = 0; i < 5; i++) a[ i ] = num;

SetEvent(hEvent1);

num++;

}

}

int main(void)

{

hEvent1 = CreateEvent(NULL, FALSE, TRUE, NULL);

hEvent2 = CreateEvent(NULL, FALSE, FALSE, NULL);

_beginthread(Thread, 0, NULL);

while(TRUE)

{

WaitForSingleObject(hEvent1, INFINITE);

printf("%d %d %d %d %d&bsol;n",

a[ 0 ], a[ 1 ], a[ 2 ],

a[ 3 ], a[ 4 ]);

SetEvent(hEvent2);

}

return 0;

}

Таймери, що чекають

Мабуть, таймери, що очікують - самий витончений об‘єкт ядра для синхронізації. З‘явились вони, починаючи з Windows 98. Таймери створюються функцією CreateWaitableTimer і бувають, також як і події, з автосбросом і без нього. Потім таймер треба настроїти функцією SetWaitableTimer. Таймер переходить в сигнальний стан, коли закінчується його таймаут. Відмінити "цокання" таймера можна функцією CancelWaitableTimer. Відмітимо, що можна указати callback функцію при установці таймера. Вона буде виконуватись, коли спрацьовує таймер.

Приклад. Напишемо програму-будильник використовуючи WaitableTimer'и. Будильник буде спрацьовувати раз в день в 8 ранку и "пікати" 10 раз. Використовуємо для цього два таймера, один з яких с callback-функцією.

#include <process.h>

#include <windows.h>

#include <stdio.h>

#include <conio.h>

#define HOUR (8) // the time of alarm

#define RINGS (10) // number of rings

HANDLE hTerminateEvent;

// callback – timer function

VOID CALLBACK TimerAPCProc(LPVOID, DWORD, DWORD)

{

Beep(1000,500); // ringing!

};

// thread function

unsigned __stdcall ThreadFunc(void *)

{

HANDLE hDayTimer = CreateWaitableTimer(NULL,FALSE,NULL);

HANDLE hAlarmTimer = CreateWaitableTimer(NULL,FALSE,NULL);

HANDLE h[2]; // we will wait for these objects

h[0] = hTerminateEvent; h[1] = hDayTimer;

int iRingCount=0; // number "rings"

int iFlag;

DWORD dw;

// we should convert the time into FILETIME format

// timer don’t understand other formats

LARGE_INTEGER liDueTime, liAllDay;

liDueTime.QuadPart=0;

// day in 100-nanosecond intervals = 10000000 * 60 * 60 * 24 = 0xC92A69C000

liAllDay.QuadPart = 0xC9;

liAllDay.QuadPart=liAllDay.QuadPart << 32;

liAllDay.QuadPart |= 0x2A69C000;

SYSTEMTIME st;

GetLocalTime(&st); // current day and time

iFlag = st.wHour > HOUR; // if the Time don’t come

// than we set alarm for today, if not than for tomorrow

st.wHour = HOUR;

st.wMinute = 0;

st.wSecond =0;

FILETIME ft;

SystemTimeToFileTime(&st, &ft);

if (iFlag)

((LARGE_INTEGER *)&ft)->QuadPart =

((LARGE_INTEGER *)&ft)->QuadPart +liAllDay.QuadPart;

LocalFileTimeToFileTime(&ft,&ft);

// Installing the timer,