Студопедия

Главная страница Случайная страница

Разделы сайта

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Використання потокової багатозадачності






11.1 Багатозадачність та її основні риси

Багатозадачність завжди вважалася необхідною рисою операційних систем. Колись, у період панування операційної системи MS-DOS, наявність графічної оболонки DosShell, яка начебто підтримувала псевдо-багатозадачність вважалося серйозним чинником, хоча, пам’ятається авторові так і не вдалося упевнитися у справжньому одночасному виконанні декількох програм за допомогою цієї оболонки.

З тих пір (початок 90-х років) комп’ютери стали швидшими на багато порядків. Наприклад така ПЕОМ, як “Іскра 1030.11” (до речі – IBM-сумісний комп’ютер) мала тактову частоту процесора у 4.77 МГц, оперативну пам’ять у 640 кілобайт та жорсткий диск у 10 мегабайт. Комп’ютер, на якому складається цей посібник – має процесор Intel Celeron 2200 МГц, оперативну пам’ять 384 мегабайти і жорсткий диск 20 гігабайт. Ці дві ПЕОМ розділяє 12 років і вели-чезний прорив у розвитку обчислювальної техніки. Можемо здогадуватися, що десь за років 5, читач з усмішкою читатиме про хвастощі автора своїм комп’ютером – на новий період безнадійно застарілим.

Зростання потужності комп’ютерів надало і нові можливості з точки зору багатозадачності. Щоправда, справжня багатозадачність залишається можливою тільки у разі дійсно паралельного виконання декількох обчислювальних операцій. Звичайний персональний комп’ютер 2005 року має лише один центральний процесор і за один такт виконує одну просту обчислювальну операцію. Це означає, що дві або більше задач мають виконуватися щонайменше у різні такти роботи процесора у певній послідовності і казати про “справжню” багатозадачність не доводиться.

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

На сьогодні багатозадачність не є примхою, а конче необхідною рисою функціонування програмного забезпечення. Справді, зараз досить важко уявити ситуацію, коли немає можливості одночасно працювати з текстом у програмі Microsoft Word, користуватися файловим менеджером, графічним редактором, одночасно переключатися між декількома Інтернет-сторінками – якщо, звісно, потужність вашого комп’ютера це дозволяє.

Таким чином, якщо комп’ютер має один центральний процесор, функції багатозадачності забезпечуються операційною системою. Microsoft Windows має два типи багатозадачності. В основі першого типу – поняття процесу (process), в основі другого – поняття потоку (thread).

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

Потік є частиною процесу, що виконується. Кожен процес складається щонайменше з одного потоку, але може містити два чи більше потоків. За потокової багатозадачності декілька частин однієї програми можуть виконуватися одночасно. Потокова багатозадачність підтримується Windows починаючи з Windows ’95 та Windows NT (до речі, Microsoft Visual C++ 1.0 не містить підтримки потокової багатозадачності).

Потокова багатозадачність надає змогу розробляти ефективні програми шляхом розділення їх на окремі виконувані блоки і керування ходом виконання усієї програми в цілому. Із використанням потокової багатозадачності виникає необхідність використання спеціального механізму, який дозволяв би контролювати виконання потоків (і процесів) у чітко визначений спосіб. Цей механізм називається синхронізацією [1, 10].

 

11.2 Створення та використання робочих потоків

 

11.2.1 Поняття потоку

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

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

У MFC визначаються два типи потоків: інтерфейсні і робочі. Інтерфейсні потоки характерні тим, що є здатними приймати та обробляти повідомлення. Тут слід згадати найпростішу MFC-програму. Вона, як пам’ятаєте, починається з оголошення об’єкта класу CApp (похідного від CWinApp). Це і є початок головного потоку програми. Якщо розглянути далі функцію InitInstance() – можна помітити, що у ній покажчик на головне вікно прикладки m_pMainWnd зв’язується з динамічно створюваним об’єктом вікна програми. Клас вікна, у свою чергу, містить оголошення карти повідомлень і відповідає за функціонування інтерфейсу користувача. Таким чином головний потік MFC програми є інтерфейсним потоком.

Робочі потоки, на відміну від інтерфейсного, не приймають і не обробляють повідомлень. Вони спрямовуються на забезпечення виконання додаткових шляхів виконання окремих завдань інтерфейсного потоку. Додатково зазначимо, що на рівні WinAPI немає різниці між робочими і інтерфейсними потоками.

У MFC потокова багатозадачність реалізується за допомогою класу CWinThread. Якщо звернутися до схеми рисунка 2.1, можна побачити, що клас CWinApp породжується від CWinThread, тобто інтерфейсні потоки є лише різновидом потоків, а робочі потоки є більш узагальненим поняттям.

Використання багатопотокового режиму роботи програми потребуватиме включення заголовкового файла “afxmt.h” (mt – розшифровується як multithreading, тобто багатопотоковість).

 

11.2.2 Створення робочих потоків

Створення робочого потоку здійснюється у досить простий спосіб. Для цього необхідно оголосити та реалізувати функцію керування потоком (потокову функцію) і здійснити її запуск.

Під час створення робочого потоку найчастіше немає потреби оголошувати спеціальний, похідний від CWinThread клас. Запуск потоку забезпечується WinAPI-функцією AfxBeginThread().

AfxBeginThread() має дві перевантажених версії прототипів: одну для забезпечення запуску робочих потоків і одну – для інтерфейсних потоків:

 

CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,

LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,

int nPriority = THREAD_PRIORITY_NORMAL,

UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

 

Параметрами цих прототипів є: pfnThreadProc – покажчик на потокову функцію робочого потоку, не може дорівнювати NULL. Потокова функція має оголошуватися у такий спосіб:

 

UINT MyControllingFunction (LPVOID pParam);

 

pThreadClass – покажчик на об’єкт динамічно створюваного класу (RUNTIME_CLASS), похідного від CWinThread;

pParam – покажчик на параметр, що передається у потокову функцію pfnThreadProc;

nPriority – бажаний рівень пріоритету потоку, значення 0 означатиме такий самий рівень пріоритету, що й у батьківського потоку, з якого здійснюється виклик створюваного потоку (рівень пріоритету потоку встановлюється за допомогою функції SetThreadPriority());

nStackSize – визначає розмір стеку пам’яті нового потоку у байтах, значення 0 встановлює такий самий розмір потоку, що й у батьківського потоку;

dwCreateFlags – прапорець режиму створення потоку, має два значення: CREATE_SUSPENDED – потік запускається припиненим (до його поновлення функцією ResumeThread()), 0 – потік негайно запускається (значення за замовчуванням);

lpSecurityAttrs – покажчик на структуру SECURITY_ATTRIBUTES, що визначає атрибути безпеки потоку, значення NULL означає, що даний потік успадковує атрибути захисту батьківського потоку.

Підсумовуючи, можна сказати, що функція AfxBeginThread() створює новий об’єкт типу CWinThread, викликає функцію CreateThread() для виконання потоку і, насамкінець, повертає покажчик на потік.

Зазвичай, потік самостійно закінчує свою роботу, хоча закінчення потоку з його середини можна викликати функцію AfxEndThread():

 

void AfxEndThread (UINT nExitCode); // nExitCode визначає код завершення потоку

 

Функції SuspendThread() та ResumeThread() відповідно припиняють та відновлюють виконання потоку, який, наприклад за значенням CREATE_SUSPENDED параметра dwCreateFlags функції AfxBeginThread() створений тимчасово припиненим. Їх прототипи мають простий вигляд:

 

DWORD CWinThread:: SuspendThread();

DWORD CWinThread:: ResumeThread();

 

Із виконанням цих функцій пов’язується поняття внутрішнього лічильника програми. Для звичайного виконуваного потоку значення лічильника дорівнює 0. Викликом SuspendThread() значення лічильника збільшується на 1, викликом ResumeThread() на 1 – знижується.

Розглянемо короткий приклад програми із одним робочим потоком. У ній відбуватиметься виклик 10 коротких звукових сигналів, що супроводжуватимуться виведенням на екран кодів цих сигналів. Для коректного збереження інформації при виведенні використовується віртуальне вікно.

Послідовність побудови програми має бути такою:

1) оголосити класи прикладки (у звичайний спосіб) та головного вікна програми відповідно до такого коду:

 

class CMain: public CFrameWnd

{public: CBrush bkBrush;

CBitmap bmp;

CDC memDC;

int m_X, m_Y;

void OnExit();

void OnPaint();

void OnLButtonDown(UINT Flags, CPoint Loc);

CMain();

DECLARE_MESSAGE_MAP()

};

 

2) оголосити глобальним прототип потокової функції:

 

UINT WorkerThread(LPVOID WinObjPtr);

 

3) реалізувати конструктор головного вікна із створенням об’єктів віртуального вікна:

 

CMain:: CMain()

{Create(NULL, " Приклад із одним потоком");

m_X=m_Y=0;

maxX=GetSystemMetrics(SM_CXSCREEN);

maxY=GetSystemMetrics(SM_CYSCREEN);

CClientDC DC(this);

memDC.CreateCompatibleDC(& DC);

bmp.CreateCompatibleBitmap(& DC, maxX, maxY);

memDC.SelectObject(& bmp);

bkBrush.CreateStockObject(WHITE_BRUSH);

memDC.SelectObject(& bkBrush);

memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

}

 

4) в обробнику натискання лівої клавіші миші викликати робочий потік:

void CMain:: OnLButtonDown(UINT flags, CPoint Loc)

{m_X=Loc.x; m_Y=Loc.y;

AfxBeginThread(WorkerThread, this); // передаємо this – покажчик на головне вікно

}

 

5) реалізувати код робочого потоку:

 

UINT WorkerThread(LPVOID WinObjPtr)

{int i;

TEXTMETRIC tm; // оголошення структури параметрів тексту

char str[255];

CMain *ptr=(CMain*) WinObjPtr; // приведення параметра WinObjPtr до типу CMain

ptr-> memDC.GetTextMetrics(& tm);

for(i=0; i< 10; i++)

{Sleep(500); // затримка у 500 мілісекунд

wsprintf(str, " Робочий потік: сигнал № %d", i);

ptr-> memDC.TextOut(ptr-> m_X, ptr-> m_Y, str, strlen(str));

ptr-> m_Y=ptr-> m_Y+tm.tmHeight+tm.tmExternalLeading;

ptr-> InvalidateRect(NULL);

MessageBeep(MB_OK);

}

ptr-> MessageBox(" Кінець роботи потоку", " End");

return 0;

}

 

Зробимо декілька зауважень до потокової функції. Її метою є виведення певного тексту у вікно програми. Однак, звичайні функції виведення тексту у прямий спосіб не спрацюють: справа в тому, що потік має глобальне оголошення і тому не є частиною класу вікна. Через це доводиться передавати покажчик на вікно через параметр типу LPVOID. Далі у самому коді потоку відбувається приведення переданого параметра WinObjPtr – до об’єкта ptr класу CMain. Маючи такий покажчик зовсім нескладно виконувати запити до характеристик віконного шрифта:

 

ptr-> memDC.GetTextMetrics(& tm);

або здійснювати виведення у саме вікно:

 

ptr-> memDC.TextOut(ptr-> m_X, ptr-> m_Y, str, strlen(str));

 

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

На рисунку 11.1 наведено результат роботи розробленої програми, а її повний текст – у прикладі 11.1.

 

 

Рисунок 10.1 – Вигляд вікна програми з одним робочим потоком

 

Приклад 11.1 – Текст програми з одним робочим потоком

// threads.h: interface for the CMain class.

 

class CMain: public CFrameWnd

{public: CBrush bkBrush;

CBitmap bmp;

CDC memDC;

int m_X, m_Y;

void OnExit();

void OnPaint();

void OnLButtonDown(UINT Flags, CPoint Loc);

CMain();

DECLARE_MESSAGE_MAP()

};

 

class CApp: public CWinApp

{public: BOOL InitInstance(); };

 

// threads.cpp: implementation of the CMain class.

 

#include < afxwin.h>

#include < afxmt.h>

#include " threads.h"

 

UINT WorkerThread(LPVOID WinObjPtr);

int maxX, maxY;

 

CMain:: CMain()

{Create(NULL, " Приклад з одним потоком ");

m_X=m_Y=0;

maxX=GetSystemMetrics(SM_CXSCREEN);

maxY=GetSystemMetrics(SM_CYSCREEN);

CClientDC DC(this);

memDC.CreateCompatibleDC(& DC);

bmp.CreateCompatibleBitmap(& DC, maxX, maxY);

memDC.SelectObject(& bmp);

bkBrush.CreateStockObject(WHITE_BRUSH);

memDC.SelectObject(& bkBrush);

memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

}

 

BEGIN_MESSAGE_MAP(CMain, CFrameWnd)

ON_WM_PAINT()

ON_WM_LBUTTONDOWN()

END_MESSAGE_MAP()

 

BOOL CApp:: InitInstance()

{m_pMainWnd=new CMain;

m_pMainWnd-> ShowWindow(m_nCmdShow);

m_pMainWnd-> UpdateWindow();

return TRUE;

}

 

CApp App;

 

void CMain:: OnLButtonDown(UINT flags, CPoint Loc)

{m_X=Loc.x; m_Y=Loc.y;

AfxBeginThread(WorkerThread, this); }

 

void CMain:: OnPaint()

{CPaintDC dc(this); dc.BitBlt(0, 0, maxX, maxY, & memDC, 0, 0, SRCCOPY); }

 

UINT WorkerThread(LPVOID WinObjPtr)

{TEXTMETRIC tm;

char str[255];

CMain *ptr=(CMain*) WinObjPtr;

ptr-> memDC.GetTextMetrics(& tm);

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

{Sleep(500);

wsprintf(str, " Робочий потік: сигнал № %d", i);

ptr-> memDC.TextOut(ptr-> m_X, ptr-> m_Y, str, strlen(str));

ptr-> m_Y=ptr-> m_Y+tm.tmHeight+tm.tmExternalLeading;

ptr-> InvalidateRect(NULL);

MessageBeep(MB_OK);

}

ptr-> MessageBox(" Кінець роботи потоку ", " End");

return 0;

}

11.3 Керування пріоритетами процесів та потоків

Кожен процес та потік має певні характеристики пріоритету. Ці характеристики визначають рівень першочерговості виконання процесу або потоку у порівнянні з іншими процесами та потоками, що одночасно виконуються операційною системою.

Microsoft Windows (як і інші операційні системи) має спеціальну системну програму – Task Manager, завданням якої є відображення стану програм, що запущені користувачем або автоматично використовуються операційною системою. Одночасно ця програма дозволяє контролювати стан завантаженості процесора, взаємодію з локальною мережею, стан користувачів системи тощо. Серед можливостей Task Manager – і контролювання рівня пріоритету процесів. Для отримання (і зміни) пріоритету програми достатньо перейти на сторінку “Processes”, обрати необхідну програму та, натиснувши праву клавішу миші увійти у спливаюче меню, де обрати “Set Priority”, як і показано на рисунку 11.2.

 

 

Рисунок 11.2 – Вигляд вікна програми Task Manager

 

Аналогічно до процесів, кожен потік має свої власні рівні пріоритетів. Рівень пріоритету є комбінацією двох значень: загального значення класу пріоритету процесу і значення пріоритету потоку, відносно пріоритету процесу.

Реальний пріоритет потоку є сумою пріоритету класу процесу і рівня пріоритету потоку. Пріоритет процесу вказує, скільки робочого часу процесора має витрачатися на його потреби (чим більший пріоритет – тим більше часу має витрачатися, чим менший – тим менше часу).

За допомогою спеціальних функцій WinAPI можна отримати інформацію про клас пріоритету процесу і встановити нове значення класу пріоритету. Ці можливості надаються функціями GetPriorityClass() і SetPriorityClass():

 

DWORD GetPriorityClass(HANDLE hProcess);

BOOL SetPriorityClass(HANDLE hProcess, DWORD dwPriorityClass);

 

де hProcess – дескриптор процесу, а dwPriorityClass – значення класу пріоритету процесу. Значення пріоритетів займають таблицю 11.1.

 

Таблиця 11.1 – Значення пріоритетів процесів

Значення Коментар
HIGH_PRIORITY_CLASS Встановлюється для процесів, виконання яких є критичним в часі і має здійснюватися першочергово у порівнянні з процесами із значеннями пріоритету NORMAL або IDLE. Приклад – Windows Task Manager. Слід обережно використовувати цей рівень, бо його використання займатиме основні ресурси центрального процесора системи.
IDLE_PRIORITY_CLASS Використовується в разі простою системи. Процес такого рівня може перериватися будь-яким іншим процесом більш високого рівня пріоритету. Прикладом є програми – зберігачі екрана (screen savers).
NORMAL_PRIORITY_CLASS Рівень пріоритету за замовчуванням. Характеризує процеси без спеціальних вимог.
REALTIME_PRIORITY_CLASS Застосовується для процесу, що має найвищий у системі пріоритет і здатний переривати роботу усіх інших процесів, включно із процесами операційної системи. Забезпечує захоплення усіх ресурсів центрального процесора.

 

Додатково зазначимо, що в операційній системі Windows 2000 з’явилися два нових класи пріоритетів: BELOW_NORMAL_PRIORITY_CLASS (нижчий від нормального) та ABOVE_NORMAL_PRIORITY_CLASS (вищий за нормальний).

Значення пріоритету процесу можна отримати, наприклад, додавши у функцію InitInstance() такі рядки:

 

HANDLE proc=GetCurrentProcess(); // отримання дескриптора процесу

SetPriorityClass(proc, IDLE_PRIORITY_CLASS); // встановлення рівня пріоритету

 

Потік, як частина процесу, має своє значення пріоритету. Пріоритет потоку означає скільки часу центрального процесора системи займається у загальному часі процесу. За замовчуванням, усі потоки створюються із нормальним (THREAD_PRIORITY_NORMAL) рівнем пріоритету.

Пріоритет потоку може бути встановлено або отримано за допомогою функцій класу CWinThread:

 

BOOL CWinThread:: SetThreadPriority (int nPriority); // встановлює пріоритет

int CWinThread:: GetThreadPriority (); // повертає значення пріоритету

 

де nPriority – встановлюваний рівень пріоритету, що має приймати такі значення:

 

THREAD_PRIORITY_TIME_CRITICAL // найвищий рівень

THREAD_PRIORITY_HIGHEST

THREAD_PRIORITY_ABOVE_NORMAL

THREAD_PRIORITY_NORMAL

THREAD_PRIORITY_BELOW_NORMAL

THREAD_PRIORITY_LOWEST

THREAD_PRIORITY_IDLE // найнижчий рівень

 

Зміст значень, перелічених від найвищого пріоритету до найнижчого, легко зрозуміти. Встановлення пріоритетів потоків також встановлюється WinAPI функціями SetThreadPriority() та GetThreadPriority(). Зазначимо, що WinAPI надає ширші можливості у встановленні пріоритетів. Зокрема, MSDN дає ін-формацію про 31 рівень пріоритетів потоків та процесів.

Функція AfxGetThread() повертає дескриптор потоку, з якого викликана. Її можна використати і під час встановлення пріоритету потоку:

 

SetThreadPriority(AfxGetThread(), THREAD_PRIORITY_ABOVE_NORMAL);

 

Нагадаємо, що рівень пріоритету потоку можна встановити при його створенні, наприклад:

 

AfxBeginThread(WorkerThread, this, THREAD_PRIORITY_ABOVE_NORMAL);

 

11.4 Синхронізація та об’єкти синхронізації

11.4.1 Загальні риси синхронізації

Багатозадачність і, включно, багатопотоковість є корисними явищами, що можуть суттєво поліпшити роботу програміста. Але така корисність не завжди є беззаперечною. Зокрема, можна легко уявити ситуацію, коли декілька потоків прагнуть отримати доступ до одного й того самого файла і одночасно здійснюють записи у цей файл, зовсім не прагнучи координувати свої дії. Або, коли одночасно завантажити на програвання комп’ютером декілька різних звукових кліпів – слухач почує їх одночасно (можливо, якщо програвати звукові файли різними програмами), але навряд чи отримає задоволення. У таких випадках багатозадачність стає на заваді цілісності зберігання або виведення інформації.

Синхронізація є спеціальним засобом організації доступу декількох потоків програми до спільних ресурсів. Уже зазначувалося, що відсутність такого механізму може призводити до небажаних та непередбачуваних результатів. MFC забезпечує створення спеціальних об’єктів синхронізації за допомогою відповідних класів.

Типова багатопотокова програма містить ресурси, що мають розподілятися між потоками – розподілені ресурси. Під час роботи з розподіленими ресурсами кожен потік може знаходитися у двох сталих ситуаціях:

1) потік може виконуватися або бути готовим до виконання;

2) потік може бути заблокованим – його виконання може бути призупиненим доки не буде звільнено необхідний ресурс або не відбудеться певна програмна подія.

 

11.4.2 Об’єкти синхронізації

Win32 підтримує 4 типи об’єктів синхронізації. Усі з них ґрунтуються на понятті семафора.

1. Класичний семафор (classic semaphore) – об’єкт синхронізації, що дозволяє обмеженій кількості потоків доступ до розподіленого ресурсу. При цьому доступ до ресурсу або обмежується повністю (тоді тільки один потік або процес матиме доступ до ресурсу продовж певного проміжку часу) або дозволяється для обмеженої кількості потоків чи процесів. Програмно семафор реалізується у вигляді лічильника, що зменшується, коли потік (процес) отримує семафор та збільшується, коли потік (процес) звільнює його.

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

3. Подія (або об’єкт події event) – об’єкт синхронізації, що здійснює повідомлення одним потоком до іншого потоку про настання певної програмної події. Використовується для блокування доступу до ресурсу, доки один потік не повідомить, що спільний ресурс може бути використано. Об’єкт події є корисним у випадку, коли потокові необхідно повідомити про дозвіл на виконання завдання.

4. Критичний розділ (critical section) – об’єкт синхронізації, що забезпечує виключне використання визначеного спільного ресурсу або частини коду програми одним потоком (процесом) та блокування усіх інших потоків (процесів). Критичні розділи є корисними, коли тільки один потік має виконувати певну функцію програми, модифікувати дані або інші контрольовані ресурси.

 

11.4.3 Класи синхронізації

До складу MFC входять шість класів, що забезпечують синхронізацію багатопотокових програм. Вони поділяються на дві частини [1]:

1) класи об’єктів синхронізації, до яких належать загальний клас CSyncObject, що є базовим класом для: CSemaphore – класичного семафора, CMutex – виключного семафора, CEvent – об’єкта події, CCriticalSection – критичного розділу;

2) допоміжні класи об’єктів забезпечення доступу: CMultiLock (забезпечує доступ до двох та більше об’єктів синхронізації) і CSingleLock (забезпечує доступ до одного об’єкта синхронізації).

Класи об’єктів синхронізації використовуються, коли для гарантування цілісності розподіленого ресурсу, програма має контролювати доступ до такого ресурсу. Класи забезпечення доступу використовуються для отримання доступу до цих керованих ресурсів. Для визначення, який клас синхронізації необхідно використовувати, MSDN рекомендує отримати відповіді на ряд запитань:

1) чи має програма очікувати на певну подію, перед тим як отримати доступ до ресурсу (наприклад, спочатку дані мають бути отримані з послідовного порту і лише потім записуватися у файл), якщо так – оберіть клас CEvent;

2) чи може більше одного потоку всередині однієї програми одночасно отримати доступ до ресурсу, якщо так – оберіть CSemaphore;

3) чи може більше однієї програми використовувати ресурс (наприклад DLL – динамічно зв’язувану бібліотеку), якщо так – використайте CMutex, якщо ні – використайте CCriticalSection.

Об’єкт класу CSingleLock є засобом контролю доступу, що використовується для керування доступом до ресурсу в багатопотоковій програмі. Для використання класів синхронізації CSemaphore, CMutex, CEvent або CCriticalSection, необхідно створити об’єкт типу CSingleLock або CMultiLock, що або очікує на об’єкт синхронізації або звільняє його. Клас CSingleLock застосовують в разі використання одного об’єкта синхронізації, клас CMultiLock – коли програма має декілька таких об’єктів.

Для використання об’єкта класу CSingleLock, викличте його конструктор, наприклад у функції-члені класу, що представляє розподілений ресурс. Конструктор класу CSingleLock має такий вигляд:

 

CSingleLock:: CSingleLock (CSyncObject* pObject, BOOL bInitialLock = FALSE);

 

де параметрами виступають: pObject – покажчик на об’єкт синхронізації, до якого має забезпечуватися доступ (не може приймати значення NULL); bInitialLock – визначає, чи має конструктор спробувати отримати доступ до об’єкта (за замовчуванням має значення NULL, тобто спроба не проводиться).

Для отримання доступу до ресурсу, що контролюється об’єктом синхронізації вказаним у конструкторі CSingleLock, викликається функція Lock():

 

BOOL CSingleLock:: Lock(DWORD dwTimeOut = INFINITE);

 

де dwTimeOut визначає час очікування об’єкта синхронізації (якщо такий час витікає, генерується помилка, якщо є такий час не визначено, встановлюється значення INFINITE). Виклик Lock() блокує доступ інших потоків до розподіленого ресурсу. Після того, як ресурс використано, викликається функція Unlock():

 

BOOL CSingleLock:: Unlock();

BOOL CSingleLock:: Unlock(LONG lCount, LPLONG lPrevCount = NULL);

 

Вже зазаначувалося, що робота об’єктів синхронізації використовує лічильники різного типу. Перший варіант Unlock() збільшує на одиницю лічильник, пов’язаний із об’єктом синхронізації, у другому варіанті параметр lCount визначає, на скільки має збільшуватися значення лічильника. Параметр lPrevCount вказує не змінну, до якої записуватиметься попереднє значення лічильника. NULL означає, що в цьому немає необхідності.

Підсумовуючи, можна визначити таку загальну процедуру керування доступом до розподіленого ресурсу:

1) створити об’єкт синхронізації (наприклад, семафор), для керування доступу до ресурсу;

2) за допомогою об’єкта синхронізації, створити об’єкт типу CSingleLock;

3) отримати доступ до ресурсу, викликавши функцію Lock();

4) виконати доступ до ресурсів;

5) викликати функцію Unlock() для звільнення ресурсу.

 

11.4.4 Робота із семафором

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

Для використання семафора насамперед необхідно створити об’єкт типу CSemaphore за допомогою конструктора класу:

 

CSemaphore:: CSemaphore(LONG lInitialCount = 1, LONG lMaxCount = 1,

LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL);

 

де параметрами є: lInitialCount – початкове значення лічильника семафора (має бути більшим або рівним 0 і меншим чи рівним lMaxCount); lMaxCount – максимальне значення лічильника (більше чи рівне 0); pstrName – ім’я семафора (визначається, коли семафор використовується об’єктами зовні процесу, NULL локалізує семафор межами одного процесу); lpsaAttributes – покажчик на параметри безпеки семафора.

Взагалі, застосовується два основних способи використання семафора.

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

Альтернативний метод використання семафора включає додавання об’єкта типу CSemaphore до класу, доступом до даних якого необхідно керувати. При цьому викликається конструктор, в якому вказуються початкове та максимальне значення лічильника, параметри безпеки та, у разі необхідності зовнішнього доступу – ім’я семафора. Після створення об’єкта програма отримує доступ до семафора. Опісля закінчення процедур доступу до ресурсів викликається функція CSyncObject:: Unlock().

Для доступу до ресурсів, контрольованих об’єктами семафора, спочатку створюється змінна типу CSingleLock або CMultiLock у функції-члені контрольованого класу. Далі викликається функція CSingleLock:: Lock(). При цьому потік або отримує доступ до ресурсу, або очікує на звільнення ресурсу або, коли час спливе, потерпає невдачі. Для звільнення ресурсу використовується функція CSingleLock:: Unlock().

Розглянемо простий приклад реалізації семафора. Нехай у програмі існують два потоки з умовними назвами FirstThread() та SecondThread() (перший та другий потоки). Обидва потоки здійснюють виведення рухомого зображення на екран: перший потік – у горизонтальному напрямку (зліва - направо), другий – у вертикальному (зверху - вниз). Рисунок 11.3 демонструє роботу першого та другого потоків.

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

 

а) б)

Рисунок 11.3 – Вікна програми із реалізацією семафора

(а – активний перший потік, б – активний другий потік)

 

Для демонстрації роботи семафора слід виконати таку послідовність кроків:

1) у класі головного вікна програми оголосити об’єкт семафора:

 

class CMain: public CFrameWnd

{public: CSemaphore Sema;

……………..

};

 

2) оголосити глобальні прототипи потокових функцій (відповідно перший та другий потоки програми):

 

UINT FirstThread(LPVOID WinObjPtr);

UINT SecondThread(LPVOID WinObjPtr);

 

3) за допомогою обробника лівої клавіші миші викликати потокові функції:

 

void CMain:: OnLButtonDown(UINT flags, CPoint Loc)

{ AfxBeginThread(SecondThread, this);

AfxBeginThread(FirstThread, this);

}

 

4) реалізувати потокові функції, наприклад, у такий спосіб:

 

UINT FirstThread(LPVOID WinObjPtr)

{BITMAP pic01;

CMain *ptr=(CMain*)WinObjPtr;

int m_X, m_Y;

CSingleLock SyncOb(& (ptr-> Sema));

CClientDC dc(ptr);

CDC memDC1;

memDC1.CreateCompatibleDC(& dc);

ptr-> pic1.GetBitmap(& pic01);

memDC1.SelectObject(ptr-> pic1);

ptr-> pic1.GetBitmap(& pic01);

while(t2< 40){

m_X=0; m_Y=100;

if(t2==0) SyncOb.Lock();

int x=m_X+pic01.bmWidth*t2, y=m_Y+pic01.bmHeight;

ptr-> memDC.BitBlt(x, y, pic01.bmWidth, pic01.bmHeight, & memDC1, 0, 0, SRCCOPY);

CRect a(x, y, x+pic01.bmWidth, y+pic01.bmHeight);

char str[255];

ptr-> InvalidateRect(a);

wsprintf(str, " %d %d Перший потік", x, y);

ptr-> SetWindowText(str);

CBrush whiteBr(RGB(255, 255, 255));

CPen wp(1, PS_SOLID, RGB(255, 255, 255));

ptr-> memDC.SelectObject(whiteBr);

ptr-> memDC.SelectObject(wp);

CRect b(x, y, x+pic01.bmWidth, y+pic01.bmHeight);

Sleep(100);

ptr-> memDC.Rectangle(b);

ptr-> InvalidateRect(b);

t2++;

if(x> maxX){t2=0; SyncOb.Unlock(); }

} return 0;

}

 

UINT SecondThread(LPVOID WinObjPtr)

{BITMAP pic;

CMain *ptr=(CMain*)WinObjPtr;

int m_X, m_Y;

CClientDC dc(ptr);

CDC memDC1;

memDC1.CreateCompatibleDC(& dc);

ptr-> pic2.GetBitmap(& pic);

CSingleLock SyncOb(& (ptr-> Sema));

memDC1.SelectObject(ptr-> pic2);

ptr-> pic2.GetBitmap(& pic);

while(t3< 50){

m_X=150; m_Y=0;

if(t3==0) SyncOb.Lock();

int x=m_X+pic.bmWidth, y=m_Y+pic.bmHeight*t3;

ptr-> memDC.BitBlt(x, y, pic.bmWidth, pic.bmHeight, & memDC1, 0, 0, SRCCOPY);

CRect a(x, y, x+pic.bmWidth, y+pic.bmHeight);

char str[255];

ptr-> InvalidateRect(a);

wsprintf(str, " %d %d Другий потік", x, y);

ptr-> SetWindowText(str);

CBrush whiteBr(RGB(255, 255, 255));

CPen wp(1, PS_SOLID, RGB(255, 255, 255));

ptr-> memDC.SelectObject(whiteBr);

ptr-> memDC.SelectObject(wp);

CRect b(x, y, x+pic.bmWidth, y+pic.bmHeight);

Sleep(100);

ptr-> memDC.Rectangle(b);

ptr-> InvalidateRect(b);

t3++;

if(y> maxY){t3=0; SyncOb.Unlock(); }

} return 0;

}

 

Серед особливостей роботи потоків визначимо такі: у кожному з потоків створюється об’єкт синхронізації, пов’язаний із семафором:

 

CSingleLock SyncOb(& (ptr-> Sema));

 

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

 

if(x> maxX){t2=0; SyncOb.Unlock(); } // для першого потоку

 

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

 

11.4.5 Робота з об’єктом події

Об’єкт класу CEvent представляє “подію” – об’єкт синхронізації, що дозволяє одному потокові повідомляти іншому про настання чи виконання певних умов, за якими робота потоків має бути перервана або, навпаки, продовжена.

Об’єкти класу CEvent породжуються у двох режимах: ручному та автоматичному. Подія, створена у ручному режимі, залишається у своєму стані, встановленому функціями SetEvent() або ResetEvent() доки не буде викликана інша функція. Автоматичний режим передбачає автоматичне повернення до невстановленого (такого, що не відбувся) стану після того, як хоча б один потік є звільненим.

Конструктор класу CEvent виглядає так:

 

CEvent:: CEvent(BOOL bInitiallyOwn=FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName=NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL);

 

де параметрами є: bInitiallyOwn – значення параметра дорівнює TRUE, потік допускається до об’єктів CMultilock або CSingleLock об’єкт (інакше, усі потоки, що бажають звертатися до ресурсу, мають чекати);

bManualReset – значення TRUE параметра визначає, що об’єкт створюється в ручному режимі, якщо FALSE – в автоматичному;

lpszName – ім’я об’єкта класу CEvent, визначається за необхідності використання об’єкта поза межами процесу (якщо задане ім’я вже існує, створюється новий об’єкт CEvent, що є посиланням на перший), NULL – порожнє ім’я;

lpsaAttribute – параметри безпеки об’єкта події.

Використання об’єкта подій за схемою подібне до використання семафора.

Спочатку створюється об’єкт класу CEvent. Визначається ім’я події та функція, яка, власне, має початково володіти ним. Виклик SetEvent() має сигналізувати про настання події. Виклик Unlock() повідомлятиме про закінчення звернення до розподіленого ресурсу.

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

Для звернення до ресурсу, що керується об’єктом типу CEvent, спочатку створюється змінна типу CSingleLock або CMultiLock. Після цього, для роботи з ресурсом викликається функція Lock() відповідного класу. При цьому потік або отримає доступ до ресурсу, або очікуватиме на звільнення ресурсу, або коли встановлений час спливе, потерпить невдачу. Виклик функції SetEvent() повідомить про настання події. Закінчення роботи із ресурсами означатиме виклик Unlock().

Розглянемо простий приклад реалізації програми. Нехай існують два потоки з умовними назвами ThirdThread() та FourthThread() (третій та четвертий потоки). Їх активність полягає у виведенні у вікно програми текстових пові-домлень про назву самого потоку та номер ітерації його виконання. Це показано на рисунку 11.4.

Функціонування програми з точки зору користувача здійснюється у такий спосіб: спочатку починає виконуватися четвертий потік, а після його 10-ї ітерації третій потік також розпочинає роботу. Далі ми пояснимо саме такий перебіг подій.

Для роботи із об’єктом події слід виконати таку послідовність кроків:

1) у класі головного вікна програми оголосити об’єкт події:

 

class CMain: public CFrameWnd

{public: CEvent Event;

……………..

};

 

2) оголосити глобальні прототипи потокових функцій (відповідно третій та четвертий потоки програми):

 

UINT ThirdThread(LPVOID WinObjPtr);

UINT FourthThread(LPVOID WinObjPtr);

 

Рисунок 11.4– Вікно програми із використання об’єкта події

 

3) з меню головного вікна програми викликати потокові функції:

 

void CMain:: OnThreads1()

{ AfxBeginThread(ThirdThread, this); // третій потік запущено першим

AfxBeginThread(FourthThread, this); }

 

4) реалізувати потокові функції, наприклад, у такий спосіб:

 

UINT ThirdThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

CSingleLock SyncOb(& (ptr-> Event));

char a[255];

SyncOb.Lock();

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

{Sleep(400);

wsprintf(a, " Третій потік %d", i);

ptr-> memDC.TextOut(1, i*15, a, strlen(a));

ptr-> InvalidateRect(NULL); }

ptr-> MessageBox(" Кінець третього потоку", " 3");

SyncOb.Unlock();

return 0; }

 

UINT FourthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255];

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

{if(i==10)ptr-> Event.SetEvent(); // встановлення події

Sleep(500);

wsprintf(a, " Четвертий потік %d", i);

ptr-> memDC.TextOut(250, i*15, a, strlen(a));

ptr-> InvalidateRect(NULL); }

ptr-> MessageBox(" Кінець четвертого потоку ", " 4");

return 0; }

 

Тепер декілька слів пояснення. Робота четвертого потоку істотно впливатиме на роботу третього потоку. Вся справа полягає у використанні об’єкта події. Третій потік, як видно з тексту програми, оголошується заблокованим до настання події:

 

CSingleLock SyncOb(& (ptr-> Event));

 

Навпаки, четвертий потік не обмежується нічим і виконується повністю, а на 10-й ітерації свого виконання встановлює подію:

 

if(i==10)ptr-> Event.SetEvent();

 

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

 

11.4.6 Робота з критичним розділом

Критичний розділ є об’єктом синхронізації, що одночасно дозволяє лише одному потоку мати доступ до ресурсу або ділянки коду програми.

Для використання критичних розділів конструюються об’єкти класу CCriticalSection. Конструктор класу параметрів не має:

 

CCriticalSection:: CCriticalSection();

 

Забезпечення доступу до ресурсів вимагає створення змінних типу CSingleLock. Виклик функції Lock() блокуватиме визначений розділ коду програми або ресурс від входження інших потоків. Після закінчення доступу до ресурсів або закінчення критичної ділянки програмного коду викликається функція Unlock().

Розглянемо простий приклад реалізації критичного розділу. Нехай існують два потоки з умовними назвами FifthThread() та SixthThread() (п’ятий та шостий потоки). Окрім них існує спільна функція Art(), що належить класу головного вікна програми і спільно використовується обома потоками. Саме Art() може бути критичним розділом: потік, що першим входить у критичний розділ, блокує функцію до закінчення своєї роботи. Другий потік зможе виконати код функції критичного розділу лише після її звільнення.

З точки зору користувача, робота програми знову-таки зводитиметься до виведення текстової інформації про те, який саме потік зараз виконується. Рисунок 11.5 демонструє роботу п’ятого та шостого потоків.

Для роботи із критичним розділом події слід виконати таку послідовність кроків:

1) у класі головного вікна програми оголосити об’єкт критичного розділу:

 

class CMain: public CFrameWnd

{public: CCriticalSection cs;

……………..

};

 

2) оголосити глобальні прототипи потокових функцій (відповідно п’ятий та шостий потоки програми):

 

UINT FifthThread(LPVOID WinObjPtr);

UINT SixthThread(LPVOID WinObjPtr);

 

Рисунок 11.5– Функціонування програми із використанням

критичного розділу

 

3) з меню головного вікна програми викликати потокові функції:

 

void CMain:: OnThreads2()

{ AfxBeginThread(FifthThread, this); // п’ятий потік запущено першим

AfxBeginThread(SixthThread, this); }

 

4) реалізувати функцію Art() і потокові функції, наприклад, у такий спосіб:

 

UINT FifthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255]=" Керування від п‘ятого потоку";

ptr-> Art(a, 1);

ptr-> MessageBox(" Кінець п‘ятого потоку ", " 5");

return 0; }

 

UINT SixthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255]=" Керування від шостого потоку ";

ptr-> Art(a, 250);

ptr-> MessageBox(" Кінець шостого потоку ", " 6");

return 0; }

 

void CMain:: Art(char *b, int r)

{char a[255];

cs.Lock();

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

{Sleep(400);

wsprintf(a, " %s %d", b, i);

memDC.TextOut(r, i*15, a, strlen(a));

InvalidateRect(NULL); }

cs.Unlock();

}

 

Як можна побачити, текст коду п’ятого та шостого потоків відрізняється зовсім незначно – лише параметрами, що передаються у функцію Art(). Ці параметри – координати виведення рядків у вікні програми (1 та 250 для п’ятого та шостого потоків відповідно), також передається рядок з іменем потоку. Сама ж функція Art() містить ділянку коду, що починається викликом cs.Lock() і закінчується викликом cs.Unlock(). Об’єкт cs і створювався у класі вікна як об’єкт критичного розділу, тому ділянку між викликами Lock() і Unlock() ми маємо вважати критичним розділом. Потік, що першим потрапить у критичний розділ, першим його і виконає, інший потік (потоки) мають очікувати на закінчення виконання критичного розділу програми. Власне, для функції критичного розділу немає різниці, який потік виконувати, тому слід вважати, що у такі функції необхідно вкладати найбільш спільні засоби функціонування програм.

Повний текст програми із використанням семафора, об’єкта події та критичного розділу наведено у прикладі 11.2

 

Приклад 11.2 – Текст програми із реалізацією потоків, об’єктів синхронізації

 

// threads.h: interface for the CMain class.

class CMain: public CFrameWnd

{public: void Art(char *, int);

CDC memDC;

CSemaphore Sema;

CEvent Event;

CCriticalSection cs;

CBitmap Bmp, pic1, pic2;

CBrush BkBrush;

void OnExit();

void OnPaint();

void OnLButtonDown(UINT Flags, CPoint Loc);

void OnThreads1();

void OnThreads2();

CMain();

DECLARE_MESSAGE_MAP()

};

 

class CApp: public CWinApp

{public: BOOL InitInstance(); };

 

// threads.cpp: implementation of the CMain class.

 

#include < afxwin.h>

#include < afxmt.h>

#include " threads.h"

#include " resource.h"

UINT FirstThread(LPVOID WinObjPtr);

UINT SecondThread(LPVOID WinObjPtr);

UINT ThirdThread(LPVOID WinObjPtr);

UINT FourthThread(LPVOID WinObjPtr);

UINT FifthThread(LPVOID WinObjPtr);

UINT SixthThread(LPVOID WinObjPtr);

int maxX, maxY;

 

CMain:: CMain()

{Create(NULL, " Програма з потоками", WS_OVERLAPPEDWINDOW, rectDefault, NULL, MAKEINTRESOURCE(IDR_MENU1));

maxX=GetSystemMetrics(SM_CXSCREEN);

maxY=GetSystemMetrics(SM_CYSCREEN);

CClientDC DC(this);

memDC.CreateCompatibleDC(& DC);

Bmp.CreateCompatibleBitmap(& DC, maxX, maxY);

memDC.SelectObject(& Bmp);

BkBrush.CreateStockObject(WHITE_BRUSH);

memDC.SelectObject(& BkBrush);

memDC.PatBlt(0, 0, maxX, maxY, PATCOPY);

pic1.LoadBitmap(IDB_BITMAP1);

pic2.LoadBitmap(IDB_BITMAP2);

}

 

BEGIN_MESSAGE_MAP(CMain, CFrameWnd)

ON_WM_PAINT()

ON_WM_LBUTTONDOWN()

ON_COMMAND(ID_THREADS1, OnThreads1)

ON_COMMAND(ID_THREADS2, OnThreads2)

ON_COMMAND(ID_EXIT, OnExit)

END_MESSAGE_MAP()

 

BOOL CApp:: InitInstance()

{m_pMainWnd=new CMain;

m_pMainWnd-> ShowWindow(m_nCmdShow);

m_pMainWnd-> UpdateWindow();

return TRUE; }

 

CApp App;

 

void CMain:: OnThreads1()

{AfxBeginThread(ThirdThread, this);

AfxBeginThread(FourthThread, this); }

 

void CMain:: OnThreads2()

{AfxBeginThread(SixthThread, this);

AfxBeginThread(FifthThread, this); }

 

int t2=0, t3=0;

 

void CMain:: OnLButtonDown(UINT flags, CPoint Loc)

{ AfxBeginThread(SecondThread, this);

AfxBeginThread(FirstThread, this); }

 

void CMain:: OnPaint()

{CPaintDC dc(this);

dc.BitBlt(0, 0, maxX, maxY, & memDC, 0, 0, SRCCOPY);

}

 

void CMain:: OnExit()

{int i=MessageBox(" Quit the program? ", " Exit", MB_YESNO);

if(i==IDYES)SendMessage(WM_CLOSE);

}

 

UINT FirstThread(LPVOID WinObjPtr)

{BITMAP pic01;

CMain *ptr=(CMain*)WinObjPtr;

int m_X, m_Y;

CSingleLock SyncOb(& (ptr-> Sema));

CClientDC dc(ptr);

CDC memDC1;

memDC1.CreateCompatibleDC(& dc);

ptr-> pic1.GetBitmap(& pic01);

memDC1.SelectObject(ptr-> pic1);

ptr-> pic1.GetBitmap(& pic01);

while(t2< 40){

m_X=0; m_Y=100;

if(t2==0) SyncOb.Lock();

int x=m_X+pic01.bmWidth*t2, y=m_Y+pic01.bmHeight;

ptr-> memDC.BitBlt(x, y, pic01.bmWidth, pic01.bmHeight, & memDC1, 0, 0, SRCCOPY);

CRect a(x, y, x+pic01.bmWidth, y+pic01.bmHeight);

char str[255];

ptr-> InvalidateRect(a);

wsprintf(str, " %d %d Перший потік", x, y);

ptr-> SetWindowText(str);

CBrush whiteBr(RGB(255, 255, 255));

CPen wp(1, PS_SOLID, RGB(255, 255, 255));

ptr-> memDC.SelectObject(whiteBr);

ptr-> memDC.SelectObject(wp);

CRect b(x, y, x+pic01.bmWidth, y+pic01.bmHeight);

Sleep(100);

ptr-> memDC.Rectangle(b);

ptr-> InvalidateRect(b);

t2++;

if(x> maxX){t2=0; SyncOb.Unlock(); }

} return 0;

}

 

UINT SecondThread(LPVOID WinObjPtr)

{BITMAP pic;

CMain *ptr=(CMain*)WinObjPtr;

int m_X, m_Y;

CClientDC dc(ptr);

CDC memDC1;

memDC1.CreateCompatibleDC(& dc);

ptr-> pic2.GetBitmap(& pic);

CSingleLock SyncOb(& (ptr-> Sema));

memDC1.SelectObject(ptr-> pic2);

ptr-> pic2.GetBitmap(& pic);

while(t3< 50){

m_X=150; m_Y=0;

if(t3==0) SyncOb.Lock();

int x=m_X+pic.bmWidth, y=m_Y+pic.bmHeight*t3;

ptr-> memDC.BitBlt(x, y, pic.bmWidth, pic.bmHeight, & memDC1, 0, 0, SRCCOPY);

CRect a(x, y, x+pic.bmWidth, y+pic.bmHeight);

char str[255]; ptr-> InvalidateRect(a);

wsprintf(str, " %d %d Другий потік", x, y);

ptr-> SetWindowText(str);

CBrush whiteBr(RGB(255, 255, 255));

CPen wp(1, PS_SOLID, RGB(255, 255, 255));

ptr-> memDC.SelectObject(whiteBr);

ptr-> memDC.SelectObject(wp);

CRect b(x, y, x+pic.bmWidth, y+pic.bmHeight);

Sleep(100);

ptr-> memDC.Rectangle(b);

ptr-> InvalidateRect(b);

t3++;

if(y> maxY){t3=0; SyncOb.Unlock(); }

} return 0;

}

 

UINT ThirdThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

CSingleLock SyncOb(& (ptr-> Event));

char a[255];

SyncOb.Lock();

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

{Sleep(400);

wsprintf(a, " Третій потік %d", i);

ptr-> memDC.TextOut(1, i*15, a, strlen(a));

ptr-> InvalidateRect(NULL); }

ptr-> MessageBox(" Кінець третього потоку", " 3");

SyncOb.Unlock();

return 0;

}

 

UINT FourthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255];

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

{if(i==10)ptr-> Event.SetEvent();

Sleep(500);

wsprintf(a, " Четвертий потік %d", i);

ptr-> memDC.TextOut(250, i*15, a, strlen(a));

ptr-> InvalidateRect(NULL);

}ptr-> MessageBox(" Кінець четвертого потоку", " 4");

return 0;

}

 

UINT FifthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255]=" Керування від п'ятого потоку";

ptr-> Art(a, 1);

ptr-> MessageBox(" Кінець п'ятого потоку", " 5");

return 0; }

 

UINT SixthThread(LPVOID WinObjPtr)

{CMain *ptr=(CMain*)WinObjPtr;

char a[255]=" Керування від шостого потоку";

ptr-> Art(a, 250);

ptr-> MessageBox(" Кінець шостого потоку", " 6");

return 0; }

 

void CMain:: Art(char *b, int r)

{int i; char a[255];

cs.Lock();

for(i=0; i< 20; i++)

{Sleep(400);

wsprintf(a, " %s %d", b, i);

memDC.TextOut(r, i*15, a, strlen(a));

InvalidateRect(NULL);

}

cs.Unlock();

}

 

11.5 Контрольні завдання

 

1. Пояснити терміни “багатозадачність” та “багатопотоковість”, їх спільні та відмінні риси.

2. Пояснити порядок створення та використання робочих потоків.

3. Пояснити можливість встановлення значення пріоритету потоку та процесу.

4. Пояснити термін “синхронізація”.

5. Пояснити відмінності об’єктів синхронізації різних типів.

6. Реалізувати програму із звичайним та виключним семафорами, пояснити порядок її функціонування.

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






© 2023 :: MyLektsii.ru :: Мои Лекции
Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав.
Копирование текстов разрешено только с указанием индексируемой ссылки на источник.