Контрольна робота
з інформатики
Особливості багатозадачності в середовищі Windows
Вступ
Основні поняття багатозадачності в Windows 95 - процес (задача) і потік (нитка). Під процесом розуміється виконання програми в цілому (WinWord, Excel, Visual C++ і т.д.) Потоками у свою чергу є частини процесу, що виконуються паралельно.
Процесом звичайно називають екземпляр програми, що виконується.
Хоча на перший погляд здається, що програма і процес поняття практично однакові, вони фундаментально відрізняються один від одного. Програма представляє собою статичний набір команд, а процес це набір ресурсів і даних, що використовуються при виконанні програми. Процес в Windows складається з наступних компонентів:
- Структура даних, що включає в себе всю інформацію про процес, в тому числі список відкритих дескрипторів різних системних ресурсів, унікальний ідентифікатор процесу, різноманітну статистичну інформацію і т.д.;
- Адресний простір - діапазон адресів віртуальної пам‘яті, яким може користуватися процес;
- Програма, що виконується і дані, що проектуються на віртуальний адресний простір процесу.
Будь який процес має хоча б один потік (у цьому випадку його можна ототожнити з потоком). Це первинний потік створюється системою автоматично при створенні процесу. Далі цей потік може породити інші потоки, ті в свою чергу нові і т.д. Таким чином, один процес може володіти декількома потоками, і тоді вони одночасно виконують код в адресному просторі процесу.
Windows краще всього працює, коли всі потоки можуть займатися своїм ділом, не взаємодіючи один з одним. Але така ситуація дуже рідкісна. Звичайно потік створюється для виконання певної роботи, про завершення якої, ймовірно, захоче узнати інший потік.
Приклад: один потік підготовляє дані, інший їх сортує, а третій виводить результат у файл. Передавши готові дані другому потоку на сортування, перший починає обробку нового блоку. Тим часом другий потік повідомляє третьому, що можна виводити результати. Роботу цих трьох потоків необхідно синхронізувати.
Всі потоки в системі повинні мати доступ до системних ресурсів — кучам, послідовним портам, файлам, вікнам і т д. Якщо один із потоків запросить монопольний доступ до якого-небудь ресурсу, іншим потокам, яким теж потрібен цей ресурс, не вдасться виконати свої задачі. А с другої сторони, просто недопустимо, щоб потоки безконтрольно користувались ресурсами. Інакше може статися так, що один потік пише в блок пам‘яті, з якого інший щось зчитує.
Потоки повинні взаємодіяти один з одним в двох основних випадках:
1) спільно використовуючи один і той же ресурс (щоб не розрушити його);
2) коли треба повідомити інші потоки про завершення яких-небудь операцій
В Windows є маса засобів, що спрощують синхронізацію потоків. Але точно спрогнозувати, в який момент потоки будуть робити то-то и то-то, надзвичайно складно.
Механізми синхронізації
Найбільш простим механізмом синхронізації є використання Interlocked-функцій. Використання цих функцій гарантує “атомарне” виконання потрібних операцій, тобто потоки не будуть заважати один одному.
Пояснимо на прикладі:
// definition of global viriable lorig g_x = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam) {
g_x++;
return(0); }
DWORD WINAPI ThreadFunc2(PVOID pvParam} {
g_x++;
return(0); }
Нема ні якої впевненості, що отримаємо двійку, тому що ми не управляємо механізмом витіснення потоків.
// definition of global viriable long g_x = 0;
DWORD WINAPI ThreadFunc1(PVOID pvParam) {
InterlockedExchangeAdd(&g_x, 1);
return(0); }
DWORD WINAPI ThreadFunc2(PVOID pvPararr) {
InterlockedExchangeAdd(&g_x, 1);
return(0); }
Тут вже можна бути впевненим, що значення g_x=2.
Розглянемо ці функції більш детально
Якщо кілька потоків мають доступ до однієї змінного, те немає ніякої гарантії, що в процесі зміни значення цієї змінний одним потоком не відбудеться переключення на інший потік, що може читати її значення. Інший потік у цьому випадку одержить невірну інформацію. Для запобігання таких конфліктів у Windows 95 уведений ряд функцій, що дозволяють коректно змінювати змінні, доступ до яких мають кілька потоків. Перелічимо функції, що охороняють від переключення під час зміни значення змінної:
LONG InterlockedIncrement (LPLONG lpAddend) - збільшує значення за адресою lpAddend на одиницю;
LONG InterlockedDecrement (LPLONG lpAddend) - зменшує значення за адресою lpAddend на одиницю;
LONG InterlockedExchange (LPLONG Target, LONG Value) - заміняє значення, що знаходиться за адресою Target, на значення, передане в параметрі Value;
LONG InterlockedExchangeAdd (PLONG Addend, LONG Increment) - додає до значення за адресою Addend значення Increment;
PVOID InterlockedCompareExchange (PVOID *Destination, PVOID Exchange, PVOID Comperand) - порівнює значення за адресою Destination зі значенням, переданим у параметрі Comperand, і якщо ці значення рівні, то за адресою Destination міститься значення, передане в параметрі Exchange.
Іншими словами, якщо в тексті програми є загальна змінна, те її зміна повинна вироблятися в такий спосіб:
{ long Val;
....
Val++; // неправильно
InterlockedIncrement(&Val); // правильно
...
}
Усі спроби зробити щось, що вимагає моментальної реакції на зовнішні події, у середовищі Windows 3.x приводили до більш ніж скромних результатів, тому що подібні програми здобували відносно стандартизований, але неповороткий графічний інтерфейс, і більше нічого. Windows 95 у принципі дозволяє розробляти критичне вчасно реакції ПО типу систем керування.
Цей метод хороший для дуже простих речей, для більш складної синхронізації він не допоможе. На щастя у Windows передбачено п'ять стандартних механізмів для синхронізації процесів і потоків:
семафор
критична секція
м’ютекс
подія
таймер
Розглянемо кожний з цих механізмів.
Критична секція
Критична секція - це частина коду, доступ до якого тепер має тільки один потік. Інший потік може звернутися до критичного розділу, тільки коли перший вийде з нього.
Для роботи з критичними секціями використовуються наступні функції:
VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection) - ініціалізація синхронізатора типу критичний розділ.
lpCriticalSection - покажчик на змінну типу CRITICAL_SECTION.
VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection) - запит на вхід у критичну секцію(розділ)
lpCriticalSection - покажчик на змінну типу CRITICAL_SECTION.
VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection) - вихід із критичного розділу (звільнення семафора).
lpCriticalSection - покажчик на змінну типу CRITICAL_SECTION.
VOID DeleteCriticalSection(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 <windows.h>
#include <process.h>
#include <stdio.h>
HANDLE hMutex;
int a[ 5 ];
void Thread(void* pParams)
{
int i, num = 0;
while (TRUE)
{
WaitForSingleObject(hMutex, INFINITE);
for (i = 0; i < 5; i++) a[ i ] = num;
ReleaseMutex(hMutex);
num++;
}
}
int main(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);
}
return 0;
}
Як видно з результату роботи процесу, основний потік (сама програма) і потік hMutex дійсно працюють паралельно (червоним кольором позначений стан, коли основний потік виводить масив під час його заповнення потоком hMutex):
81751652 81751652 81751651 81751651 81751651
81751652 81751652 81751651 81751651 81751651
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629
83348630 83348630 83348630 83348629 83348629
Приклад. Допустимо, в програмі використовується ресурс, наприклад, файл або буфер в пам‘яті. Функція WriteToBuffer() викликається з різних потоків. Щоб уникнути колізій при одночасному зверненні до буферу з різних потоків, використовуємо м‘ютекс. Перед тим як звернутися до буфера, очікуємо „звільнення” м‘ютекса.
HANDLE hMutex;
int main()
{
hMutex = CreateMutex(NULL, FALSE, NULL); // Создаем мьютекс в свободном состоянии
//...
// Создание потоков, и т.д.
//...
}
BOOL WriteToBuffer()
{ DWORD dwWaitResult;
// Ждем освобождения мьютекса перед тем как обратиться к буферу.
dwWaitResult = WaitForSingleObject(hMutex, 5000L); // 5 секунд на таймаут
if (dwWaitResult == WAIT_TIMEOUT) // Таймаут. Мьютекс за єто время не освободился.
{
return FALSE;
}
else // Мьютекс освободился, и наш поток его занял. Можно работать.
{
Write_to_the_buffer().
...
ReleaseMutex(hMutex); // Освобождаем мьютекс.
}
return TRUE;
Семафор
Ще один вид синхронізаторів - семафор, що виключає. Основна його відмінність від критичної секції полягає в тім, що останню можна використовувати тільки в межах одного процесу (одного запущеного додатка), а семафорами, що виключають, можуть користатися різні процеси.
Semaphore – глобальний об’єкт синхронізації, що має лічильник для ресурсів, з ним пов‘язаних. В достатньо грубому приближенні м‘ютекс можна розглядати, як частковий випадок семафора з двома станами.
Іншими словами, критичні розділи - це локальні семафори, що доступні в рамках тільки однієї програми, а семафори, що виключають, можуть бути глобальними об'єктами, що дозволяють синхронізувати роботу програм (тобто різні запущені додатки можуть розділяти ті самі дані).
Розглянемо основні функції семафора, що виключає, на прикладі роботи з об'єктами mutex.
1. Створення об'єкта mutex
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName)
lpMutexAttributes - покажчик на структуру SECURITY_ATTRIBUTES (у Windows 95 даний параметр ігнорується);
bInitialOwner - указує первісний стан створеного об'єкта (TRUE - об'єкт відразу стає зайнятим, FALSE - об'єкт вільний);
lpName - указує на рядок, що містить ім'я об'єкта. Ім'я необхідне для доступу до об'єкта інших процесів, у цьому випадку об'єкт стає глобальним і їм можуть оперувати різні програми. Якщо вам не потрібний іменований об'єкт, то вкажіть NULL. Функція повертає покажчик на об'єкт mutex. Надалі цей покажчик використовується для керування семафором, що виключає.
... широка рекламна кампанія, всі виробники ПЗ почали переробляти свої продукти для цієї нової ОС, виробники комп’ютерів і комплектуючих намагались отримати логотип Designed for Windows 95. Причина, по якій Windows 95 опинилася в центрі загальної уваги, проста: це було саме важливе оновлення системи Windows з часів появи в 1990 році Windows 3.0. Користувачі тепер отримували переваги об’єктно – оріє ...
... та знизу ( нижній колонтитул ) у межах одного розділу або всього документа. Правильний вибір цієї інформації дає змогу читачеві краще орієнтуватися в документі. 5.4 Уведення інформації Інформаційна система маркетингу – це сукупність інформації, апаратно-програмних і технологічних засобів, засобів телекомунікацій, баз і банків даних, методів і процедур, персоналу управління, які реалізують ...
... і додатки, такі як Mіcrosoft SQL Server і Exchange Server, також включають компоненти, що працюють як служби NT. · Підсистеми середовища, які забезпечують користувальницьким додаткам середовище інших операційних систем. Wіndows NT поставляється із трьома підсистемами: Wіn32, Posіx і OS/2 2.1. · Користувальницькі додатки одного з п'яти типів: Wіn32, Wіndows 3.1, MS-DOS, Posіx або OS/2 ...
... . Розділ “Керівництво користувача” призначений для вивчення , використання і засвоєння пакету програм по вирішенню задачі “Облік наявності та руху кадрiв”. Далі цей пакет програм буде називатися ПП “Кадр”. У розділі наведені основні відомості для роботи у середовищі автоматизованої системи, поданий опис режимів і функцій ПП, порядку дій користувача. Сукупність програмних засобів забезпечує ...
0 комментариев