Контрольна робота
з інформатики
Особливостібагатозадачності в середовищі Windows
Вступ
Основніпоняття багатозадачності в Windows 95 — процес (задача) і потік (нитка). Підпроцесом розуміється виконання програми в цілому (WinWord, Excel, Visual C++ іт.д.) Потоками у свою чергу є частини процесу, що виконуються паралельно.
Процесомзвичайно називають екземпляр програми, що виконується.
Хочана перший погляд здається, що програма і процес поняття практично однакові,вони фундаментально відрізняються один від одного. Програма представляє собоюстатичний набір команд, а процес це набір ресурсів і даних, що використовуютьсяпри виконанні програми. Процес в Windows складається з наступних компонентів:
— Структура даних, що включає в себе всю інформацію про процес, в тому числі списоквідкритих дескрипторів різних системних ресурсів, унікальний ідентифікаторпроцесу, різноманітну статистичну інформацію і т.д.;
— Адресний простір — діапазон адресів віртуальної пам‘яті, яким можекористуватися процес;
— Програма, що виконується і дані, що проектуються на віртуальний адреснийпростір процесу.
Будьякий процес має хоча б один потік (у цьому випадку його можна ототожнити зпотоком). Це первинний потік створюється системою автоматично при створенніпроцесу. Далі цей потік може породити інші потоки, ті в свою чергу нові і т.д.Таким чином, один процес може володіти декількома потоками, і тоді вониодночасно виконують код в адресному просторі процесу.
Windowsкраще всього працює, коли всі потоки можуть займатися своїм ділом, невзаємодіючи один з одним. Але така ситуація дуже рідкісна. Звичайно потікстворюється для виконання певної роботи, про завершення якої, ймовірно, захочеузнати інший потік.
Приклад:один потік підготовляє дані, інший їх сортує, а третій виводить результат уфайл. Передавши готові дані другому потоку на сортування, перший починаєобробку нового блоку. Тим часом другий потік повідомляє третьому, що можнавиводити результати. Роботу цих трьох потоків необхідно синхронізувати.
Всіпотоки в системі повинні мати доступ до системних ресурсів — кучам, послідовнимпортам, файлам, вікнам і т д. Якщо один із потоків запросить монопольний доступдо якого-небудь ресурсу, іншим потокам, яким теж потрібен цей ресурс, невдасться виконати свої задачі. А с другої сторони, просто недопустимо, щобпотоки безконтрольно користувались ресурсами. Інакше може статися так, що одинпотік пише в блок пам‘яті, з якого інший щось зчитує.
Потокиповинні взаємодіяти один з одним в двох основних випадках:
1)спільно використовуючи один і той же ресурс (щоб не розрушити його);
2)коли треба повідомити інші потоки про завершення яких-небудь операцій
ВWindows є маса засобів, що спрощують синхронізацію потоків. Але точноспрогнозувати, в який момент потоки будуть робити то-то и то-то, надзвичайноскладно.
Механізмисинхронізації
Найбільшпростим механізмом синхронізації є використання Interlocked-функцій.Використання цих функцій гарантує “атомарне” виконання потрібних операцій,тобто потоки не будуть заважати один одному.
Пояснимона прикладі:
// definitionof global viriable lorig g_x = 0;
DWORDWINAPI ThreadFunc1(PVOID pvParam) {
g_x++;
return(0);}
DWORDWINAPI ThreadFunc2(PVOID pvParam} {
g_x++;
return(0);}
Немані якої впевненості, що отримаємо двійку, тому що ми не управляємо механізмомвитіснення потоків.
//definition of global viriable long g_x = 0;
DWORDWINAPI ThreadFunc1(PVOID pvParam) {
InterlockedExchangeAdd(&g_x,1);
return(0);}
DWORDWINAPI ThreadFunc2(PVOID pvPararr) {
InterlockedExchangeAdd(&g_x,1);
return(0);}
Тутвже можна бути впевненим, що значення g_x=2.
Розглянемоці функції більш детально
Якщокілька потоків мають доступ до однієї змінного, те немає ніякої гарантії, що впроцесі зміни значення цієї змінний одним потоком не відбудеться переключенняна інший потік, що може читати її значення. Інший потік у цьому випадкуодержить невірну інформацію. Для запобігання таких конфліктів у Windows 95уведений ряд функцій, що дозволяють коректно змінювати змінні, доступ до якихмають кілька потоків. Перелічимо функції, що охороняють від переключення підчас зміни значення змінної:
LONGInterlockedIncrement (LPLONG lpAddend) — збільшує значення за адресою lpAddendна одиницю;
LONGInterlockedDecrement (LPLONG lpAddend) — зменшує значення за адресою lpAddendна одиницю;
LONGInterlockedExchange (LPLONG Target, LONG Value) — заміняє значення, щознаходиться за адресою Target, на значення, передане в параметрі Value;
LONGInterlockedExchangeAdd (PLONG Addend, LONG Increment) — додає до значення заадресою Addend значення Increment;
PVOIDInterlockedCompareExchange (PVOID *Destination, PVOID Exchange, PVOIDComperand) — порівнює значення за адресою Destination зі значенням, переданим упараметрі Comperand, і якщо ці значення рівні, то за адресою Destinationміститься значення, передане в параметрі Exchange.
Іншимисловами, якщо в тексті програми є загальна змінна, те її зміна повиннавироблятися в такий спосіб:
{ longVal;
....
Val++;// неправильно
InterlockedIncrement(&Val);// правильно
...
}
Усіспроби зробити щось, що вимагає моментальної реакції на зовнішні події, усередовищі Windows 3.x приводили до більш ніж скромних результатів, тому щоподібні програми здобували відносно стандартизований, але неповороткийграфічний інтерфейс, і більше нічого. Windows 95 у принципі дозволяє розроблятикритичне вчасно реакції ПО типу систем керування.
Цейметод хороший для дуже простих речей, для більш складної синхронізації він недопоможе. На щастя у Windows передбачено п'ять стандартних механізмів длясинхронізації процесів і потоків:
семафор
критичнасекція
м’ютекс
подія
таймер
Розглянемокожний з цих механізмів.
Критичнасекція
/>
Критичнасекція — це частина коду, доступ до якого тепер має тільки один потік. Іншийпотік може звернутися до критичного розділу, тільки коли перший вийде з нього.
Дляроботи з критичними секціями використовуються наступні функції:
VOIDInitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) — ініціалізаціясинхронізатора типу критичний розділ.
lpCriticalSection- покажчик на змінну типу CRITICAL_SECTION.
VOIDEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) — запит на вхід укритичну секцію(розділ)
lpCriticalSection- покажчик на змінну типу CRITICAL_SECTION.
VOIDLeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) — вихід ізкритичного розділу (звільнення семафора).
lpCriticalSection- покажчик на змінну типу CRITICAL_SECTION.
VOIDDeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection) — видаленнякритичного розділу (звичайно при виході з програми).
lpCriticalSection- покажчик на змінну типу CRITICAL_SECTION.
Отже,для створення критичного розділу необхідно ініціалізувати структуруCRITICAL_SECTION. Що Windows у цій структурі зберігає, нас не стосується — важливо, що покажчик на цю структуру ідентифікує наш семафор.
Створившиоб'єкт CRITICAL_SECTION, ми можемо працювати з ним, тобто можемо позначити код,доступ до якого для одночасно виконуються задач потрібно синхронізувати.
Розглянемотакий приклад. Ми хочемо записувати і зчитувати значення з деякого глобальногомасиву mas. Причому запис і зчитування повинні вироблятися двома різнимипотоками. Цілком природно, що краще якщо ці дії не будуть виконуватисяодночасно. Тому введемо обмеження на доступ до масиву.
Іхоча приведений нами приклад подібного обмеження (см.лістинг 1)надзвичайноспрощений, він добре ілюструє роботу синхронізатора типу критичний розділ: покиодин потік «володіє» масивом, інший доступу до нього не має.
М‘ютекси(взаємовиключення)
М’ютекс(взаємовиключення, mutex) — це об’єкт синхронізації, який установлюється вособливий сигнальний стан, коли не зайнятий яким-небудь потоком. Тільки одинпотік володіє цим об’єктом в любий момент часу, звідси и назва таких об‘єктів –одночасний доступ до спільного ресурсу виключається. Наприклад, щоб виключитизапис двох потоків в спільний участок пам’яті в один і то й же час, кожнийпотік очікує, коли звільниться м’ютекс, стає його власником и тільки потім пишещось в цю ділянку пам’яті. Після всіх необхідних дій м’ютекс звільняється,надаючи іншим потокам доступ до спільного ресурсу.
Два(або більше) процесів можуть створити м‘ютекс з одним і тим же іменем, визвавшиметод CreateMutex. Перший процес дійсно створює м’ютекс, а наступні процесиотримують хендл існуючого вже об‘єкта. Це дає можливість декільком процесам отриматихендл одного і того ж м’ютекса, звільняючи програміста від необхідностітурбуватися про те, хто насправді створює м’ютекс. Якщо використовується такийпідхід, бажано встановити флаг bInitialOwner в FALSE, інакше виникнуть певнітруднощі при визначенні справжнього “творця” м’ютекса.
Декількапроцесів можуть отримати хендл (handle) одного й того ж м‘ютекса, що робитьможливим взаємодію між процесами. Ви можете використовувати наступні механізмитакого підходу:
Дочірнійпроцес, створений за допомогою функції CreateProcess може наслідувати хендлм‘ютекса у випадку, якщо при його (м‘ютекса) створенні функцією CreateMutex буввказаний параметр lpMutexAttributes.
Процесможе отримати дублікат існуючого м‘ютекса з допомогою функції DuplicateHandle.
Процесможе вказати ім‘я існуючого м‘ютекса при виклику функцій OpenMutex абоCreateMutex.
#include
#include
#include
HANDLEhMutex;
inta[ 5 ];
voidThread(void* pParams)
{
inti, num = 0;
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
for (i= 0; i
ReleaseMutex(hMutex);
num++;
}
}
intmain(void)
{
hMutex= CreateMutex(NULL, FALSE, NULL);
_beginthread(Thread,0, NULL);
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
printf("%d%d %d %d %d\n",
a[ 0], a[ 1 ], a[ 2 ],
a[ 3], a[ 4 ]);
ReleaseMutex(hMutex);
}
return0;
}
Яквидно з результату роботи процесу, основний потік (сама програма) і потікhMutex дійсно працюють паралельно (червоним кольором позначений стан, колиосновний потік виводить масив під час його заповнення потоком hMutex):
8175165281751652 81751651 81751651 81751651
8175165281751652 81751651 81751651 81751651
8334863083348630 83348630 83348629 83348629
8334863083348630 83348630 83348629 83348629
8334863083348630 83348630 83348629 83348629
Приклад.Допустимо, в програмі використовується ресурс, наприклад, файл або буфер впам‘яті. Функція WriteToBuffer() викликається з різних потоків. Щоб уникнутиколізій при одночасному зверненні до буферу з різних потоків, використовуємом‘ютекс. Перед тим як звернутися до буфера, очікуємо „звільнення” м‘ютекса.
HANDLEhMutex;
intmain()
{
hMutex= CreateMutex(NULL, FALSE, NULL); // Создаем мьютекс в свободном состоянии
//...
//Создание потоков, и т.д.
//...
}
BOOLWriteToBuffer()
{ DWORDdwWaitResult;
//Ждем освобождения мьютекса перед тем как обратиться к буферу.
dwWaitResult= WaitForSingleObject(hMutex, 5000L); // 5 секунд на таймаут
if(dwWaitResult == WAIT_TIMEOUT) // Таймаут. Мьютекс за єто время не освободился.
{
returnFALSE;
}
else// Мьютекс освободился, и наш поток его занял. Можно работать.
{
Write_to_the_buffer().
...
ReleaseMutex(hMutex);// Освобождаем мьютекс.
}
returnTRUE;
Семафор
Щеодин вид синхронізаторів — семафор, що виключає. Основна його відмінність відкритичної секції полягає в тім, що останню можна використовувати тільки в межаходного процесу (одного запущеного додатка), а семафорами, що виключають, можутькористатися різні процеси.
Semaphore– глобальний об’єкт синхронізації, що має лічильник для ресурсів, з нимпов‘язаних. В достатньо грубому приближенні м‘ютекс можна розглядати, якчастковий випадок семафора з двома станами.
Іншимисловами, критичні розділи — це локальні семафори, що доступні в рамках тількиоднієї програми, а семафори, що виключають, можуть бути глобальними об'єктами,що дозволяють синхронізувати роботу програм (тобто різні запущені додаткиможуть розділяти ті самі дані).
Розглянемоосновні функції семафора, що виключає, на прикладі роботи з об'єктами mutex.
1.Створення об'єкта mutex
HANDLECreateMutex(
LPSECURITY_ATTRIBUTESlpMutexAttributes,
BOOLbInitialOwner,
LPCTSTRlpName)
lpMutexAttributes- покажчик на структуру SECURITY_ATTRIBUTES (у Windows 95 даний параметрігнорується);
bInitialOwner- указує первісний стан створеного об'єкта (TRUE — об'єкт відразу стаєзайнятим, FALSE — об'єкт вільний);
lpName- указує на рядок, що містить ім'я об'єкта. Ім'я необхідне для доступу дооб'єкта інших процесів, у цьому випадку об'єкт стає глобальним і їм можутьоперувати різні програми. Якщо вам не потрібний іменований об'єкт, то вкажітьNULL. Функція повертає покажчик на об'єкт mutex. Надалі цей покажчиквикористовується для керування семафором, що виключає.
2.Закриття (знищення) об'єкта mutex
BOOLCloseHandle(HANDLE hObject)
3.Універсальна функція запиту доступу
DWORDWaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) — універсальнафункція, призначена для запиту доступу до синхронізуючого об'єкту (у даномувипадку до об'єкта mutex).
hHandle- покажчик на синхронізуючий об'єкт (у даному випадку передається значення,повернуте функцією CreateMutex);
dwMilliseconds- час (у міллісекундах), протягом якого відбувається чекання звільнення об'єктаmutex. Якщо передати значення INFINITE (нескінченність), то функція буде чекатинескінченно довго.
Данафункція може повертати наступні значення:
WAIT_OBJECT_0- об'єкт звільнився;
WAIT_TIMEOUT- час чекання звільнення пройшов, а об'єкт не звільнився;
WAIT_ABANDON- відбулося відмовлення від об'єкта (тобто процес, що володіє даним об'єктом,завершилося, не звільнивши об'єкт). У цьому випадку система (а не«процес-власник») переводить об'єкт у вільний стан. Таке звільненняоб'єкта не припускає гарантій у захищеності даних;
WAIT_FAILED- відбулася помилка.
4. Звільненняоб'єкта mutex
BOOLReleaseMutex(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
#include
#include
HANDLEhEvent1, hEvent2;
inta[ 5 ];
voidThread(void* pParams)
{
inti, num = 0;
while(TRUE)
{
WaitForSingleObject(hEvent2,INFINITE);
for (i= 0; i
SetEvent(hEvent1);
num++;
}
}
intmain(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\n",
a[ 0], a[ 1 ], a[ 2 ],
a[ 3], a[ 4 ]);
SetEvent(hEvent2);
}
return0;
}
Таймери,що чекають
Мабуть,таймери, що очікують — самий витончений об‘єкт ядра для синхронізації.З‘явились вони, починаючи з Windows 98. Таймери створюються функцієюCreateWaitableTimer і бувають, також як і події, з автосбросом і без нього.Потім таймер треба настроїти функцією SetWaitableTimer. Таймер переходить всигнальний стан, коли закінчується його таймаут. Відмінити «цокання»таймера можна функцією CancelWaitableTimer. Відмітимо, що можна указатиcallback функцію при установці таймера. Вона буде виконуватись, коли спрацьовуєтаймер.
Приклад.Напишемо програму-будильник використовуючи WaitableTimer'и. Будильник будеспрацьовувати раз в день в 8 ранку и «пікати» 10 раз. Використовуємодля цього два таймера, один з яких с callback-функцією.
#include
#include
#include
#include
#defineHOUR (8) // the time of alarm
#defineRINGS (10) // number of rings
HANDLEhTerminateEvent;
//callback – timer function
VOIDCALLBACK TimerAPCProc(LPVOID, DWORD, DWORD)
{
Beep(1000,500);// ringing!
};
//thread function
unsigned__stdcall ThreadFunc(void *)
{
HANDLEhDayTimer = CreateWaitableTimer(NULL,FALSE,NULL);
HANDLEhAlarmTimer = CreateWaitableTimer(NULL,FALSE,NULL);
HANDLEh[2]; // we will wait for these objects
h[0]= hTerminateEvent; h[1] = hDayTimer;
int iRingCount=0;// number «rings»
intiFlag;
DWORDdw;
// weshould convert the time into FILETIME format
//timer don’t understand other formats
LARGE_INTEGERliDueTime, liAllDay;
liDueTime.QuadPart=0;
//day in 100-nanosecond intervals = 10000000 * 60 * 60 * 24 = 0xC92A69C000
liAllDay.QuadPart= 0xC9;
liAllDay.QuadPart=liAllDay.QuadPart
liAllDay.QuadPart|= 0x2A69C000;
SYSTEMTIMEst;
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;
FILETIMEft;
SystemTimeToFileTime(&st,&ft);
if(iFlag)
((LARGE_INTEGER*)&ft)->QuadPart =
((LARGE_INTEGER*)&ft)->QuadPart +liAllDay.QuadPart;
LocalFileTimeToFileTime(&ft,&ft);
//Installing the timer,
// itwill alarm once a day
SetWaitableTimer(hDayTimer,(LARGE_INTEGER *) &ft, 24*60*60000, 0, 0, 0);
do {
dw =WaitForMultipleObjectsEx(2,h,FALSE,INFINITE,TRUE);
if(dw == WAIT_OBJECT_0 +1) // hDayTimer
{
SetWaitableTimer(hAlarmTimer,&liDueTime, 1000, TimerAPCProc, NULL, 0);
iRingCount=0;
}
if(dw == WAIT_IO_COMPLETION) // the callback-functionhas finished working
{
iRingCount++;
if(iRingCount==RINGS)
CancelWaitableTimer(hAlarmTimer);
}
}while(dw!= WAIT_OBJECT_0); // while hTerminateEvent is of
CancelWaitableTimer(hDayTimer);
CancelWaitableTimer(hAlarmTimer);
CloseHandle(hDayTimer);
CloseHandle(hAlarmTimer);
_endthreadex(0);
return0;
};
intmain(int argc, char* argv[])
{
//this event shows the thread when to finish working
hTerminateEvent= CreateEvent(NULL,FALSE,FALSE,NULL);
unsigneduThreadID;
HANDLEhThread;
//creating thread
hThread= (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, 0, 0,&uThreadID);
puts(«Pressany key to exit.»);
getch();
//setting the event
SetEvent(hTerminateEvent);
//waitingfor closing of the thread
WaitForSingleObject(hThread,INFINITE);
CloseHandle(hThread);
return0;}
Багатопотоковістьі графіка
Є щеодна особливість при роботі з об'єктами синхронізації. Справа в тім, що Windows95 досить «важко» взаємодіє зі своєю графічною системою вбагатозадачному режимі. Це пояснюється тим, що в Windows 95 графічна підсистемачастково залишилася 16-розрядної і звертання до такого коду приводить дозахоплення системного семафора, що виключає, Win16Mutex, що запобігаєодночасний доступ декількох процесів (потоків) до такого коду. Твердженняавторів деяких книг по Windows 95 про те, що це не є перешкодою, якщо випрацюєте в цілком 32-розрядному додатку, на практиці виявляється неспроможним.
Отже,основною проблемою стала неможливість коректного зняття з виконання графічногопотоку. Зняття вироблялося по наступному алгоритму. Кожен потік у нескінченномуциклі перевіряв прапор-сигнал про завершення. Якщо прапор був виставлений, топотік виходив з нескінченного циклу і завершувався штатним шляхом. У спрощеномувиді процедура зняття описана в лістингу 3.
Такийкод ідеально працював, якщо вироблялося зняття потоку, що не звертається дографічної системи Windows (або рідко звертається — раз у кілька секунд). Якщо жпотік увесь час що-небудь малював, то спроба зняття закінчувалася виходом зфункції WaitForSingleObject через перевищення часу чекання (значення,щоповертається, WAIT_TIMEOUT), тобто підпрограма, що знімається, не одержувалакерування, поки ми «сиділи» у функції WaitForSingleObject. Збільшенняперіоду чекання (наприклад, до 10 с) ні до чого не приводило — потік усі десятьсекунд уперто чекав звільнення об'єкта і зрештою виходив зі значеннямWAIT_TIMEOUT.
Причина,по якій потік не знімався, узагалі ж зрозуміла — йому не передавалосякерування. Можна спробувати примусово зробити це, збільшивши пріоритет потоку,що знімається:
voidbreakTask(GF_Task* tsk)
{
DWORDresult;
chars[512];
//команда потоку, що знімається, на зняття tsk->putState(tsBreak,True);
//збільшуємо відносний пріоритет
//потоку, що знімається, до максимально можливого
SetThreadPriority(tsk->TaskHnd95,THREAD_PRIORITY_TIME_CRITICAL)
//чекаємо завершення потоку протягом 1 з
WaitForSingleObject(tsk->TaskHnd95,1000);
}
Результатуніякого (вірніше, результат той же — вихід зі значенням WAIT_TIMEOUT).Виходить, що підвищення пріоритету не завжди спрацьовує (ще однією докірMicrosoft).
Що жробити? Як змусити потік, у якому працює програма зняття breakTask, передатикерування іншим потокам? При одержанні значення WAIT_TIMEOUT починаєвиконуватися та частина коду, що виводить на екран вікно з запитом про те, що жробити з потоком, що не знімається. У момент висновку вікна на екранбагатостраждальний потік раптом сам завершується — він нарешті«зауважує» прапорець завершення і виходить з нескінченного циклу. Цепідтверджує, що до потоку, що знімається, просто не доходить керування (невиділяється квант часу).
Невдаючись у причини подібного поводження Windows, ми повинні проаналізувати, ащо ж усе-таки відбувається в модальному вікні, що змушує ОС помітити нашузадачу. Імовірно, усе криється в петлі чекання подій, що запускається вмодальному вікні. Однієї з основних функцій у такому циклі чекання є функціяGetMessage. Чудовою властивістю володіє дана функція: її виклик приводить дооповіщення планувальника задач Windows. Оскільки зовнішніх подій для потоку, щовикликав цю функцію, ні, те частину, що залишилася, його кванта часупланувальник задач передає іншому потоку, що виконується. Таким чином, нашпотік, що знімається, знову оживає.
Отже,нам треба використовувати функцію типу GetMessage для стимуляції Windows допередачі керування іншим потокам. Але сама функція GetMessage нам не підходить,тому що вона віддає керування тільки в тому випадку, якщо для потоку з'явилосяповідомлення. Замість GetMessage можна застосувати функцію PeekMessage, щоперевіряє, є чи повідомлення в черзі для даного потоку, і незалежно відрезультату відразу ж повертає керування. Перепишемо наш попередній приклад так:
voidbreakTask(GF_Task* tsk)
{
DWORDresult;
chars[512];
//команда потоку, що знімається, на зняття
tsk->putState(tsBreak,True);
//збільшуємо його відносний пріоритет
// домаксимально можливого
SetThreadPriority(tsk->TaskHnd95,
THREAD_PRIORITY_TIME_CRITICAL)
intcnt = 1000/20;
//чекаємо завершення потоку протягом приблизно 1 з
while(cnt-)
{ //стимулюємо Windows до передачі кванта часу
//іншим потокам
PeekMessage(&_Msg,0,0,0,PM_NOREMOVE);
//чекаємо завершення потоку
result= WaitForSingleObject(tsk->TaskHnd95,20);
//якщо все-таки не дочекалися,
// тевиходимо з циклу чекання
if(result!=WAIT_TIMEOUT)
break;
}
...іт.д.
}
Удокументації по SDK затверджується, що для передачі кванта часу іншим потокамможна викликати функцію Sleep з параметром 0 (Sleep(0)). Тому в літературі посистемному програмуванню рекомендують для стимуляції Windows до передачі квантачасу синхронізувати потоки, використовуючи функцію PeekMessage.
Лістинг1. Обмеження доступу до масиву з використанням критичних розділів
//Масив значень.
intmas[1000];
//Семафор, що регулює доступ до критичного розділу.
CRITICAL_SECTIONCritSec;
{
...
//Инициализируем семафор критичного розділу.
InitializeCriticalSection(&CritSect);
…// Текст програми.
//Видаляємо об'єкт критичного розділу.
DeleteCriticalSection(&CritSec);
}
//Перший потік: запис у масив даних.
DWORDthread1(LPVOID par)
{ //Запис значення в масив.
//Запит на вхід у критичний розділ.
EnterCriticalSection(&CritSec);
//Виконання коду в критичному розділі.
for(inti = 0;i
{
mas[i]= i;
}
//Вихід із критичного розділу:
//звільняємо критичний розділ для доступу
// донього інших задач.
LeaveCriticalSection(&CritSec);
return0;
}
//Другий потік: зчитування даних з масиву.
DWORDthread2(LPVOID par)
{ //Зчитування значень з масиву.
intj;
//Запит на вхід у критичний розділ.
EnterCriticalSection(&CritSec);
//Виконання коду в критичному розділі.
for(inti = 0;i
{
j =mas[i];
}
// Вихідіз критичного розділу:
//звільняємо критичний розділ для доступу
// донього інших задач.
LeaveCriticalSection(&CritSec);
return0;
}
Лістинг2. Обмеження доступу до масиву з використанням семафорів, що виключають
//Масив значень.
intmas[1000];
//Об'єкт, що регулює доступ до поділюваного коду.
HANDLECritMutex;
{
...
//Инициализируем семафор поділюваного коду.
CritMutex= SectCreateMutex(NULL,FALSE,NULL);
…// Текст програми.
//Закриваємо об'єкт доступу до поділюваного коду.
CloseHandle(CritMutex);
}
//Перший потік: запис у масив даних.
DWORDthread1(LPVOID par)
{ //Запис значень у масив.
//Запит на вхід у захищений розділ.
DWORDdw = WaitForSingleObject(CritMutex,INFINITE);
if(dw== WAIT_OBJECT_0)
{ //Якщо об'єкт звільнений коректно, те
//виконання коду в захищеному розділі.
for(inti = 0;i
{
mas[i]= i;
}
//Вихід із захищеного розділу:
//звільняємо об'єкт для доступу
// дозахищеного розділу інших задач.
ReleaseMutex(CritMutex);
}
return0;
}
//Другий потік: зчитування даних з масиву.
DWORDthread2(LPVOID par)
{ //Зчитування значень з масиву.
intj;
//Запит на вхід у захищений розділ.
DWORDdw = WaitForSingleObject(CritMutex,INFINITE);
if(dw== WAIT_OBJECT_0)
{ //Якщо об'єкт звільнений коректно, те
// виконаннякоду в захищеному розділі.
for(inti = 0;i
{
j =mas[i];
}
//Вихід із захищеного розділу:
//звільняємо об'єкт для доступу
// дозахищеного розділу інших задач.
ReleaseMutex(CritMutex);
}
return0;
}
Лістинг3. Зняття графічного потоку
voidbreakTask(GF_Task* tsk)
{
DWORDresult;
chars[512];
//Команда задачі, що знімається, на зняття.
tsk->putState(tsBreak,True);
//Чекаємо завершення потоку протягом 1 с.
WaitForSingleObject(tsk->TaskHnd95,1000);
//
//Аналіз відповіді.
//
if(result== WAIT_OBJECT_0) // Ok — потік довершений успішно.
{
result= cmOK;
goto _L_EndbreakTask;
}
elseif(result == WAIT_TIMEOUT) // Потік не відповідає.
{ //Підготовляємо рядок запиту.
sprintf(s,,
«Потік# %і не відповідає...\nобїект %s\n Зробіть вибір: \n\
'Так'- повторити команду на зняття \n\
'Немає'- зняти потік примусово \n\
'Скасувати'- не знімати потік»
TaskCollection->indexOf(tsk)+1,
tsk->getName());
}
//Висновок запиту на екран.
result= MsgBox(s, msg|msgSound);
switch(result)// Аналіз відповіді.
{
casecmNo: // Примусове зняття потоку.
tsk->putState(tsCrash,True);// Виставляємо прапор
tsk->endTask();// Заключні операції
TerminateThread(tsk->TaskHnd95,0);// Знімаємо потік
goto _L_EndbreakTask;
casecmCancel: // Скасування зняття потоку.
goto _L_EndbreakTask;
}
}
elseif(WAIT_FAILED) // Відбулася помилка доступу до об'єкта.
{ //Примусове зняття потоку.
SIL();// Звуковий сигнал
tsk->endTask();// Заключні операції
TerminateThread(tsk->TaskHnd95,0);
SIL();// Звуковий сигнал
result= cmNo;
goto _L_EndbreakTask;
}
}
_L_EndbreakTask:
CloseHandle(tsk->TaskHnd95);
tsk->TaskHnd95= 0;
tsk->putState(tsWorkTask,False);// Знімаємо прапори
returnresult;
}
//Код потоку, що знімається, приблизно наступний:
DWORDthread1(LPVOID par)
{
while((state& tsBreak) == 0)
{ //Поки прапор tsBreak не виставлений, виконуємо потік.
draw()// Щось виводимо на екран.
}
return0;
}
Завданняна виконання
Використовуючикомпілятор С++, або Assembler реалізувати програму синхронізації згідноваріанту. Під час роботи програми треба весь час виводити інформацію про те якпрацюють потоки (у файл, або реалізувати графічну візуалізацію). Якщо у Вашомузавданні мова іде про розподіл ресурсів між потоками треба фіксувати звільненняі зайняття ресурсів потоками. При використанні механізму подій та семафорівтреба фіксувати переходи у вільні стани (події) та переходи у сигнальний стан(таймери).
Написатиз використанням подій програму, що реалізують таку схему за допомогою подій.Нехай є клієнт та сервер, які повинні спілкуватись між собою. Спочатку сервер(це один потік) просто чекає. Клієнт (другий потік) приймає у користувачаповідомлення та передає його серверу. Сервер обробляє повідомлення, а саме: вінформує нове повідомлення яке складається з n повторів початкового повідомлення,де n – кількість символів у початковому повідомленні. Весь цей час клієнтчекає, потім отримує відповідь і виводить її користувачу.
Виконатизавдання №1 використовуючи механізм таймерів. (треба виставити достатній часдля обробки запитів.)
Написатипрограму, що має два потока; один готує дані (наприклад зчитує з файла), аінший відсилає їх на сервер. Треба розпаралелити їх роботу. Тут потоки повинніпрацювати по черзі. Спочатку перший потік готує порцію даних. Потім другийпотік відправляє її, а перший тим часом готує наступну порцію і т.д. (для такоїсинхронізації потрібно буде дві події “автосбросом”).
Написатиз використанням м’ютекса програму, що створює десять процесів, кожен з яких усвою чергу створює десять потоків, у кожному з потоків треба підрахуватизначення факторіалу сумі свого номеру (a) та номеру свого процесу (b), потімзанести номер свого процесу (1-10), свій номер (1-10), та обчислене значення уфайл (вхід у стек). Після цього кожен потік повинен обчислити значення ступенюa^b. Знову, аналогічним чином відмітитись у файлі та, якщо можна, залишитивідмітку про вихід зі стеку. Якщо вийти зі стеку неможливо чекати і потімвийти. Таким чином кожен потік пише у файл два рази або один раз. Переконатисьу тому, що є потоки, які не чекають своєї черги вийти зі стеку. (Стек повиненспрацювати за стандартним правилом: перший зайшов – останній вийшов.)
Виконатизавдання №4 використовуючи механізм подій (детально продумайте схему,прийдеться використовувати досить багато подій).
Виконатизавдання №4 використовуючи критичні секції.
Виконатизавдання №4 використовуючи семафор.
Виконатизавдання №4 з тією різницею, що потоки повинні створити чергу. (Черга працює заправилом FIFO. Перший зайшов – перший вийшов. Поки не вийдуть всі потоки передданним, він повинен чекати).
Виконатизавдання №5 з тією різницею, що потоки повинні створити чергу. (Черга працює заправилом FIFO. Перший зайшов – перший вийшов. Поки не вийдуть всі потоки передданним, він повинен чекати).
Виконатизавдання №6 з тією різницею, що потоки повинні створити чергу. (Черга працює заправилом FIFO. Перший зайшов – перший вийшов. Поки не вийдуть всі потоки передданним, він повинен чекати).
Виконатизавдання №7 з тією різницею, що потоки повинні створити чергу. (Черга працює заправилом FIFO. Перший зайшов – перший вийшов. Поки не вийдуть всі потоки передданним, він повинен чекати).
Написатипрограму (використовувати м’ютекс та семафор), що створює 5 клієнтських та 2серверних потока. Для кожного потока зафіксувати деякий період часу, який вінчекає, а потім поміщає у чергу свій номер та номер свого запиту (1,2 і т.д.).Серверні потоки нічого не роблять поки у черзі не з’явиться хоча б одинєлемент. Як тільки він з’явився один з серверних потоків обробляє його (пише у файлсвій номер та отриманий запит). Після обробки потік “засинає” на фіксованийчас. Якщо в черзі більше 10 запитів програма зупиняється.
Підібратитакі значення часових проміжків, щоб черга переповнювалась.
Написатипрограму (використовуючи критичні секції та семафор), що аналогічна до програмиз завдання №12. Кількість клієнтів — 2, кількість серверів – 4.
Використовуючиподію з ручним сбросом реалізувати наступне: Ваша програма повинна створити двапотока. Один пише у файл (можна працювати просто з пам’ятю) 1000 одиниць іпотім у циклі читає цей файл та змінює 1 на 0 та навпаки. (Після другої«прогонки» у файлі будуть лише нулю, потім тільки одиниці і т.д.). Другий потікповинен «просинатися» через фіксовані проміжки часу та підраховувати кількістьодиниць у файлі.
Задопомогою м’ютекса та таймера реалізувати наступне: Один потік просинаєтьсячерез однакові проміжки часу та додає 1 за модулем десять до числа у файлі(спочатку там нуль). Ваша програма повинна змоделювати будильник, що пікаєкожну годину таке число разів, скільки записано у файлі.
Виконатизавдання №15 використовуючи критичні секції та таймера.
Виконатизавдання №15 використовуючи семафори та таймери.
Використовуючиподію і таймер написати програму, що створює два потока, що «працюють» по черзі(так само як у завданні №1, але потоком не треба обмінюватись повідомленнями).Ваша програма повинна моделювати будильник, який кожну годину дивиться який зпроцесів працює (№1 або №2) та пікає 1 або 2 рази відповідно.
Закритичної секції та подій написати програму, що створює 11 потоків. Кожен з 10перших потоків підраховує числа 10^10, 20^9, 30^8, 40^8, 50^7, 60^6, 70^5,80^4, 90^2, 100^1 відповідно. Їх роботу необхідно синхронізувати таким чином,щоб вони по черзі писали результат у файл, а одинадцятий потік послідовнозчитував ці числа та додавав їх.
Написатипрограму з завдання №19 із використанням семафорів та подій.
Завданняна виконання
Використовуючикомпілятор С++, або Assembler реалізувати програму синхронізації згідноваріанту:
1.Перевірититвердження SDK про те, що потоки мають дисципліну захоплення мютекса заправилом FIFO
2.Перевіритипорядок виконання потоків після сигналізації події(event)
3.Написати бібліотеку роботи з комплексними числами в багатопоточному середовищі задопомогою критичної секції (одне блокування на всі потоки)
4.Написатибібліотеку роботи з комплексними числами в багатопоточному середовищі задопомогою повязаного з комплексним числом м’ютекса
5.Написатибібліотеку роботи з комплексними числами в багатопоточному середовищі задопомогою пов‘язаного з комплексним числом мютекса(окремо читання і для запису)
6.Написати бібліотеку роботи з векторами в багатопоточному середовищі задопомогою критичної секції (одне блокування на всі потоки)
7.Написатибібліотеку роботи з векторами в багатопоточному середовищі за допомогоюповязаного з вектором м’ютекса
8.Написатибібліотеку роботи з векторами в багатопоточному середовищі за допомогоюпов’язаного з вектором м’ютекса(окремо читання і для запису)
9.З‘ясуватидисципліну виконання потоків, які стояли в черзі по мірі звільнення семафора
10.Організуватисинхронізацію між іменованими створеними обєктами за рахунок подій(event)
11.Написатибібліотеку, яка узагальнює поняття критичної секції на міжпроцесній взаємодії
12.Написатибібліотеку роботи зі стеком в багатопоточному середовищі за допомогоюповязаного з вектором м’ютекса
13.Написатибібліотеку роботи зі стеком в багатопоточному середовищі за допомогою критичноїсекції (одне блокування на всі потоки)
14.Написатибібліотеку роботи зі стеком в багатопоточному середовищі за допомогоюпов’язаного з вектором м’ютекса(окремо читання і для запису)