Смекни!
smekni.com

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

// it will 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);

return 0;

};

int main(int argc, char* argv[])

{

// this event shows the thread when to finish working

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

unsigned uThreadID;

HANDLE hThread;

// creating thread

hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, 0, 0,&uThreadID);

puts("Press any key to exit.");

getch();

// setting the event

SetEvent(hTerminateEvent);

//waiting for closing of the thread

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);

return 0;}

Багатопотоковість і графіка

Є ще одна особливість при роботі з об'єктами синхронізації. Справа в тім, що Windows 95 досить "важко" взаємодіє зі своєю графічною системою в багатозадачному режимі. Це пояснюється тим, що в Windows 95 графічна підсистема частково залишилася 16-розрядної і звертання до такого коду приводить до захоплення системного семафора, що виключає, Win16Mutex, що запобігає одночасний доступ декількох процесів (потоків) до такого коду. Твердження авторів деяких книг по Windows 95 про те, що це не є перешкодою, якщо ви працюєте в цілком 32-розрядному додатку, на практиці виявляється неспроможним.

Отже, основною проблемою стала неможливість коректного зняття з виконання графічного потоку. Зняття вироблялося по наступному алгоритму. Кожен потік у нескінченному циклі перевіряв прапор-сигнал про завершення. Якщо прапор був виставлений, то потік виходив з нескінченного циклу і завершувався штатним шляхом. У спрощеному виді процедура зняття описана в лістингу 3.

Такий код ідеально працював, якщо вироблялося зняття потоку, що не звертається до графічної системи Windows (або рідко звертається - раз у кілька секунд). Якщо ж потік увесь час що-небудь малював, то спроба зняття закінчувалася виходом з функції WaitForSingleObject через перевищення часу чекання (значення, щоповертається, WAIT_TIMEOUT), тобто підпрограма, що знімається, не одержувала керування, поки ми "сиділи" у функції WaitForSingleObject. Збільшення періоду чекання (наприклад, до 10 с) ні до чого не приводило - потік усі десять секунд уперто чекав звільнення об'єкта і зрештою виходив зі значенням WAIT_TIMEOUT.

Причина, по якій потік не знімався, узагалі ж зрозуміла - йому не передавалося керування. Можна спробувати примусово зробити це, збільшивши пріоритет потоку, що знімається:

void breakTask(GF_Task* tsk)

{

DWORD result;

char s[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, що перевіряє, є чи повідомлення в черзі для даного потоку, і незалежно від результату відразу ж повертає керування. Перепишемо наш попередній приклад так:

void breakTask(GF_Task* tsk)

{

DWORD result;

char s[512];

// команда потоку, що знімається, на зняття

tsk->putState(tsBreak,True);

// збільшуємо його відносний пріоритет

// до максимально можливого

SetThreadPriority(tsk->TaskHnd95,

THREAD_PRIORITY_TIME_CRITICAL)

int cnt = 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. Обмеження доступу до масиву з використанням критичних розділів

// Масив значень.

int mas[1000];

// Семафор, що регулює доступ до критичного розділу.

CRITICAL_SECTION CritSec;

{

...

// Инициализируем семафор критичного розділу.

InitializeCriticalSection(&CritSect);

... // Текст програми.

// Видаляємо об'єкт критичного розділу.

DeleteCriticalSection(&CritSec);

}

// Перший потік: запис у масив даних.

DWORD thread1(LPVOID par)

{ // Запис значення в масив.

// Запит на вхід у критичний розділ.

EnterCriticalSection(&CritSec);

// Виконання коду в критичному розділі.

for(int i = 0;i<1000;i++)

{

mas[i] = i;

}

// Вихід із критичного розділу:

// звільняємо критичний розділ для доступу

// до нього інших задач.

LeaveCriticalSection(&CritSec);

return 0;

}

// Другий потік: зчитування даних з масиву.

DWORD thread2(LPVOID par)

{ // Зчитування значень з масиву.

int j;

// Запит на вхід у критичний розділ.

EnterCriticalSection(&CritSec);

// Виконання коду в критичному розділі.

for(int i = 0;i<1000;i++)

{

j = mas[i];

}

// Вихід із критичного розділу:

// звільняємо критичний розділ для доступу

// до нього інших задач.

LeaveCriticalSection(&CritSec);

return 0;

}

Лістинг 2. Обмеження доступу до масиву з використанням семафорів, що виключають

// Масив значень.

int mas[1000];

// Об'єкт, що регулює доступ до поділюваного коду.

HANDLE CritMutex;

{

...

// Инициализируем семафор поділюваного коду.

CritMutex = SectCreateMutex(NULL,FALSE,NULL);

... // Текст програми.

// Закриваємо об'єкт доступу до поділюваного коду.

CloseHandle(CritMutex);

}

// Перший потік: запис у масив даних.

DWORD thread1(LPVOID par)

{ // Запис значень у масив.

// Запит на вхід у захищений розділ.

DWORD dw = WaitForSingleObject(CritMutex,INFINITE);

if(dw == WAIT_OBJECT_0)

{ // Якщо об'єкт звільнений коректно, те

// виконання коду в захищеному розділі.

for(int i = 0;i<1000;i++)

{

mas[i] = i;

}

// Вихід із захищеного розділу:

// звільняємо об'єкт для доступу

// до захищеного розділу інших задач.

ReleaseMutex(CritMutex);

}

return 0;

}

// Другий потік: зчитування даних з масиву.

DWORD thread2(LPVOID par)

{ // Зчитування значень з масиву.

int j;

// Запит на вхід у захищений розділ.

DWORD dw = WaitForSingleObject(CritMutex,INFINITE);

if(dw == WAIT_OBJECT_0)

{ // Якщо об'єкт звільнений коректно, те

// виконання коду в захищеному розділі.

for(int i = 0;i<1000;i++)

{

j = mas[i];

}

// Вихід із захищеного розділу:

// звільняємо об'єкт для доступу

// до захищеного розділу інших задач.

ReleaseMutex(CritMutex);

}

return 0;

}

Лістинг 3. Зняття графічного потоку

void breakTask(GF_Task* tsk)

{

DWORD result;

char s[512];

// Команда задачі, що знімається, на зняття.

tsk->putState(tsBreak,True);

// Чекаємо завершення потоку протягом 1 с.

WaitForSingleObject(tsk->TaskHnd95,1000);

//

// Аналіз відповіді.

//

if(result == WAIT_OBJECT_0) // Ok - потік довершений успішно.

{

result = cmOK;

goto _L_EndbreakTask;

}

else if(result == WAIT_TIMEOUT) // Потік не відповідає.

{ // Підготовляємо рядок запиту.

sprintf(s,,

"Потік # %і не відповідає...&bsol;nобїект %s&bsol;n Зробіть вибір: &bsol;n&bsol;

'Так' - повторити команду на зняття &bsol;n&bsol;

'Немає' - зняти потік примусово &bsol;n&bsol;

'Скасувати' - не знімати потік"

TaskCollection->indexOf(tsk)+1,

tsk->getName());

}

// Висновок запиту на екран.

result = MsgBox(s, msg|msgSound);

switch(result) // Аналіз відповіді.

{

case cmNo: // Примусове зняття потоку.

tsk->putState(tsCrash,True); // Виставляємо прапор

tsk->endTask(); // Заключні операції

TerminateThread(tsk->TaskHnd95,0); // Знімаємо потік

goto _L_EndbreakTask;

case cmCancel: // Скасування зняття потоку.

goto _L_EndbreakTask;

}

}

else if(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); // Знімаємо прапори

return result;

}

// Код потоку, що знімається, приблизно наступний:

DWORD thread1(LPVOID par)

{

while((state & tsBreak) == 0)

{ // Поки прапор tsBreak не виставлений, виконуємо потік.

draw() // Щось виводимо на екран.

}

return 0;

}

Завдання на виконання

Використовуючи компілятор С++, або Assembler реалізувати програму синхронізації згідно варіанту. Під час роботи програми треба весь час виводити інформацію про те як працюють потоки (у файл, або реалізувати графічну візуалізацію). Якщо у Вашому завданні мова іде про розподіл ресурсів між потоками треба фіксувати звільнення і зайняття ресурсів потоками. При використанні механізму подій та семафорів треба фіксувати переходи у вільні стани (події) та переходи у сигнальний стан (таймери).