50496
Взаємодія між потоками
Лабораторная работа
Информатика, кибернетика и программирование
Мета: Засвоїти поняття паралельного виконання «потоків» та освоїти засоби їх синхронізації. Здобути навики синхронізації «потоків» при обробці спільних даних та доступу до ресурсів в операційній системі Windows.
Украинкский
2014-01-24
90 KB
6 чел.
Лабораторна робота № 2
Тема: «Взаємодія між потоками»
Мета: Засвоїти поняття паралельного виконання «потоків» та освоїти засоби їх синхронізації. Здобути навики синхронізації «потоків» при обробці спільних даних та доступу до ресурсів в операційній системі Windows.
1. Теоретична частина
Windows надає чотири обєкти, призначені для синхронізації потоків і процесів. Три з них мютекси, семафори і події є обєктами ядра, що мають дескриптори. Події використовуються також для інших цілей, наприклад, для асинхронного уведення-виведення.
Спочатку розглянемо четвертий обєкт, а саме, обєкт критичної ділянки коду CRITICAL_SECTION. Через простоту і продуктивність, обєктам критичних ділянок коду надається перевага, якщо їх можливостей достатньо для того, щоб задовольнити вимоги програміста.
1.1. Об'єкти критичних ділянок коду
Обєкт критичної ділянки коду це ділянка програмного коду, який кожного разу повинен виконуватися тільки одним потоком; паралельне виконання цієї ділянки декількома потоками може приводити до непередбачуваних або невірних результатів.
Обєкти CRITICAL_SECTION (CS) можна ініціалізувати і видаляти, але вони не мають дескрипторів і не можуть спільно використовуватися іншими процесами. Відповідні змінні повинні оголошуватися як змінні типу CRITICAL_SECTION. Потоки входять в обєкти CS і покидають їх, але виконання коду окремого обєкту CS кожного разу дозволено тільки одному потоку. Разом з тим, один і той же потік може входити в декілька окремих обєктів CS і покидати їх, якщо вони розташовані в різних місцях програми.
Для ініціалізації і видалення змінної типу CRITICAL_SECTION використовуються, відповідно, функції InitializeCriticalSection() і DeleteCritical-Section():
VOID InitializeCriticalSection(
LPCRITICAL_SECTION ipCriticalSection)
VOID DeleteCriticalSection(
LPCRITICAL_SECTION IpCriticalSection}
Функція EnterCriticalSection() блокує потік, якщо на даній критичній ділянці коду присутній інший потік. Потік, що очікує розблокуєся після того, як інший потік виконає функцію LeaveCriticalSection(). Говорять, що потік отримав права володіння обєктом CS, якщо відбулося повернення з функції EnterCriticalSection(), тоді як для того щоб віддати права володіння використовується функція LeaveCriticalSection(). Завжди необхідно стежити за своєчасною переуступкою прав володіння обєктами CS; недотримання цього правила може привести до того, що інші потоки перебуватимуть в стані очікування протягом невизначеного часу навіть після завершення виконання потоку-власника.
VOID EnterCriticalSection (
LPCRITICAL_SECTION IpCriticalSection)
VOID LeaveCriticalSection (
LPCRITICAL_SECTION IpCriticalSection)
Потік, що володіє об'єктом CS, може повторно увійти до цього ж CS без його блокування: це означає, що обєкти CRITICAL_SECIION є рекурсивними (recursive). Підтримується лічильник входжень в обєкт CS, і тому потік повинен покинути дану CS стільки раз, скільки було входжень в неї, щоб розблокувати цей обєкт для інших потоків. Ця можливість може виявитися корисною для реалізації рекурсивних функцій і забезпечення безпечного багатопотокового виконання функцій загальних бібліотек.
Вихід з, обєкту CS, яким даний потік не володіє, може призвести до непередбачуваних результатів, включаючи блокування самого потоку.
Для повернення з функції EnterCriticalSection() не існує кінцевого інтервалу очікування; інші потоки будуть блоковані на невизначений час, поки потік, що володіє обєктом CS, не покине його. Проте, використовуючи функцію TryEnterCriticalSection(), можна тестувати (опитати) CS, щоб перевірити, чи не володіє ним інший потік.
BOOL TryEnterCriticalSection (
LPCRITICAL_SECTION ipCriticalSection)
Повернення функцією TryEnterCriticalSection() значення true означає, що цей потік придбав права володіння критичною ділянкою коду, тоді як повернення значення false говорить про те, що дана критична ділянка коду вже належить іншому потокові.
Обєкти CRITICAL_SECTION мають ту перевагу, що вони не є обєктами ядра і підтримуються в призначеному для користувача просторі. Звичайно, але не завжди, це приводить до додаткового поліпшення показників продуктивності.
1.2. Мютекси
Обєкт взаємного виключення (mutual exception), або мютекс (mutex), забезпечує більш універсальну функціональність в порівнянні з обєктом CRITICAL_SECTION. Оскільки мютекси можуть мати імена і дескриптори, їх можна використовувати також для синхронізації потоків, що належать різним процесам. Так, два процеси, що розділяють спільну память за допомогою відображення файлів, можуть використовувати мютекси для синхронізації доступу до областей памяті, що розділяються.
Обєкти мютексів аналогічні обєктам CS, проте, додатково до можливості їх сумісного використовування різними процесами, вони допускають кінцеві періоди очікування, а мютекси, що покинуті (abandoned) процесом який завершився переходять в сигнальний стан. Потік придбаває права володіння мютексом (або блокує (block) мютекс) шляхом виклику функції очікування (WaitForSingleObject() або WaitForMultipleObjects()) по відношенню до дескриптора мютексу і поступається цими правами за допомогою виклику функції ReleaseMutex().
Як завжди, необхідно ретельно стежити за тим, щоб потоки своєчасно звільняли ресурси, в яких вони більше не мають потреби. Потік може заволодівати одним і тим же ресурсом кілька разів, і при цьому не блокуватиметься навіть в тих випадках, коли вже володіє даним ресурсом. Кінець кінцем, потік повинен звільнити мютекс стільки раз, скільки він його захоплювала. При роботі з мютексами використовуються функції CreateMutex(), ReleaseMutex() і OpenMutex().
HANDLE CreateMutex (LPSECURITY_ATTRIBUTES lpsa
BOOL blnitialOwner, LPCTSTR lpMutexName)
BOOL ReleaseMutex (HANDLE hMutex)
blnitialOwner якщо значення цього прапора встановлено рівним true, потік негайно придбаває права володіння новим мютексом. Ця атомарна операція дозволяє запобігти придбанню прав володіння мютексом іншими потоками, перш ніж це зробить потік, що створив мьютекс. Як випливає з самого нього назви (initial owner початковий власник), цей прапор не надає ніякої дії, якщо мютекс вже існує. lpMutexName покажчик на рядок, що містить імя мютексу; на відміну від файлів імена мютексів чутливі до регістру. Якщо цей параметр рівний NULL, то мьютекс створюється без імені. Події, мютекси, семафори, відображення файлів і інші обєкти ядра використовують один і той же простір імен, відмінний від простору імен файлової системи. Тому імена всіх обєктів синхронізації повинні бути різними. Довжина вказаних імен не може перевищувати 260 символів.
Значення, що повертається, має тип HANDLE; значення NULL указує на невдале завершення функції.
Функція OpenMutex() відкриває існуючий іменований мютекс. Ця функція дає можливість потокам, що належать різним процесам, синхронізуватися так, як якби вони належали одному і тому ж процесу. Виклику функції OpenMutex() в одному процесі повинен передувати виклик функції CreateMutex() в іншому процесі. При виклику функції OpenMutex() завжди передбачається, що спочатку один процес, наприклад сервер, викликає функцію Create() для створення іменованого обєкту, а потім інші процеси викликають функцію Open(), яка завершується невдачею, якщо іменований обєкт до цього моменту ще не був створений. Можливий і такий варіант, коли всі процеси самостійно використовують виклик функції Create() з одним і тим же імям, якщо порядок створення обєктів не має значення.
Функція ReleaseMutex() звільняє мютекс, яким володіє цей потік. Якщо мютекс не належить потоку, функція завершується з помилкою.
BOOL ReleaseMutex (HANDLE hMutex)
Мютекс, яким потік володів та завершившись не звільнив його, називають покинутим (abandoned), і його дескриптор переходить в сигнальний стан. На те, що дескриптор (дескриптори) представляє покинутий мютекс (мютекси), указує повернення функцією WaitForSingleObject() значення WAIT_ABANDONED_0.
Те, що дескриптори покинутих мютексів переходять в сигнальний стан, є вельми корисною їх властивістю, недоступною для обєктів CS. Виявлення покинутого мютексу може означати наявність дефекту в коді, що організовує роботу потоків, оскільки потоки повинні програмуватися так, щоб ресурси завжди звільнялися, перш ніж потік завершить своє виконання. Можливо також, що виконання даного потоку було перервано іншим потоком.
1.3. Семафори
Обєкти другого з трьох згаданих на початку типів обєктів синхронізації ядра семафори (semaphores), підтримують лічильники, і коли значення цього лічильника більше 0, обєкт семафора знаходиться в сигнальному стані. Якщо ж значення лічильника стає нульовим, обєкт семафора переходить в несигнальний стан.
Потоки і процеси організовують очікування звичайним способом, використовуючи для цього одну або декілька функцій очікування.
До функцій управління семафорами відносяться CreateSemaphore(), QpenSemaphore() і ReleaseSemaphore(), причому остання функція може збільшити начення лічильника на 1 і більше. Ці функції аналогічні своїм еквівалентам, призначеним для управління мютексими.
HANDLE CreateSemaphore (LPSECURITY^ATTRIBUTES lpsa
LONG lSemlnitial, LONG lSemMax, LPCTSTR lpSemName)
Параметр lSemMax, значення якого повинне бути рівним, принаймні 1, визначає максимально допустиме значення лічильника семафора. Параметр lSemlnitial початкове значення цього лічильника, яке повинне задовольняти наступній умові: 0 < lSemlnitial < lSemMax і ніколи не повинно виходити за межі вказаного діапазону. Повернення функцією значення NULL указує на її невдале виконання.
Кожна окрема операція очікування може зменшити значення лічильника тільки на 1, але за допомогою функції ReleaseSemaphore() значення його лічильника може бути збільшено до будь-якого значення аж до максимально допустимого.
BOOL ReleaseSemaphore ( HANDLE hSemaphore
LONG cReleaseCount, LPLONG lpPreviousCount)
Зверніть увагу на можливість отримання попереднього значення лічильника, що визначається покажчиком lpPreviousCount, яке він мав до звільнення обєкту синхронізації за допомогою функції ReleaseSemaphore(), але якщо необхідності в цьому нема, то значення згаданого покажчика слід встановити рівним NULL.
Число, що додається до лічильника семафора (cReleaseCount), повинне бути більше 0, але якщо виконання функції ReleaseSemaphore() приводить до виходу значення лічильника за межі допустимого діапазону, то вона завершується з помилкою, повертаючи значення FALSE, а значення лічильника семафора залишається незмінним. Попереднім значенням лічильника слід користуватися з обережністю, оскільки воно могло бути змінено іншими потоками. Крім того, неможливо визначити, чи досяг лічильник максимально допустимого значення, оскільки не був передбачений засіб, що відстежує збільшення лічильника в результаті його звільнення.
Як не спокусливо намагатися розглядати мютекс як окремий випадок семафора, значення лічильника якого було задано рівним 1, це було б помилкою зважаючи на відсутність поняття прав володіння семафором. Семафор може бути звільнений будь-яким потоком, а не тільки тим, який чекає. Так само, оскільки не можна говорити про права володіння семафором, відсутнє і поняття покинутого семафора.
Класичною областю вживання семафорів є управління розподілом кінцевих ресурсів, коли значення лічильника семафора асоціюється з певною кількістю доступних ресурсів, наприклад, кількістю повідомлень, що знаходяться в черзі. Тоді максимальне значення лічильника відповідає максимальному розміру черги. Таким чином, виробник поміщає повідомлення в буфер і викликає функцію ReleaseSemaphore(), звичайно із збільшенням значення лічильника на 1 (cReleaseCount). Потоки споживача чекатимуть переходу семафора в сигнальний стан, одержуючи повідомлення і зменшуючи значення лічильника.
Не дивлячись на всі зручності, які обіцяє використовування семафорів, вони є в деякому розумінні зайвими в тому значенні, що мютекси і події, за умови їх сумісного використовування, пропонують набагато більш широкі можливості, ніж семафори.
2. Завдання на лабораторну роботу
1. Дослідити роботу програми в середовищі Visual Studio, що демонструє використання обєктів критичних ділянок коду та приведена нижче.
/* Maintain two threads, a producer and a consumer */
/* The producer periodically creates checksummed data buffers, */
/* or "message block" which the consumer displays when prompted */
/* by the user. The conusmer reads the most recent complete */
/* set of data and validates it before display */
#include "EvryThng.h"
#include <time.h>
#define DATA_SIZE 256
typedef struct msg_block_tag { /* Message block */
volatile DWORD f_ready, f_stop;
/* ready state flag, stop flag */
volatile DWORD sequence; /* Message block sequence number */
volatile DWORD nCons, nLost;
time_t timestamp;
CRITICAL_SECTION mguard; /* Guard the message block structure */
DWORD checksum; /* Message contents checksum */
DWORD data[DATA_SIZE]; /* Message Contents */
} MSG_BLOCK;
/* One of the following conditions holds for the message block */
/* 1) !f_ready || f_stop */
/* nothing is assured about the data OR */
/* 2) f_ready && data is valid */
/* && checksum and timestamp are valid */
/* Also, at all times, 0 <= nLost + nCons <= sequence */
/* Single message block, ready to fill with a new message */
MSG_BLOCK mblock = { 0, 0, 0, 0, 0 };
DWORD WINAPI produce (void *);
DWORD WINAPI consume (void *);
void MessageFill (MSG_BLOCK *);
void MessageDisplay (MSG_BLOCK *);
DWORD _tmain (DWORD argc, LPTSTR argv[])
{
DWORD Status, ThId;
HANDLE produce_h, consume_h;
/* Initialize the message block CRITICAL SECTION */
InitializeCriticalSection (&mblock.mguard);
/* Create the two threads */
produce_h = (HANDLE)_beginthreadex (NULL, 0, produce, NULL, 0, &ThId);
if (produce_h == NULL)
ReportError (_T("Cannot create producer thread"), 1, TRUE);
consume_h = (HANDLE)_beginthreadex (NULL, 0, consume, NULL, 0, &ThId);
if (consume_h == NULL)
ReportError (_T("Cannot create consumer thread"), 2, TRUE);
/* Wait for the producer and consumer to complete */
Status = WaitForSingleObject (consume_h, INFINITE);
if (Status != WAIT_OBJECT_0)
ReportError (_T("Failed waiting for consumer thread"), 3, TRUE);
Status = WaitForSingleObject (produce_h, INFINITE);
if (Status != WAIT_OBJECT_0)
ReportError (__T("Failed waiting for producer thread"), 4, TRUE);
DeleteCriticalSection (&mblock.mguard);
_tprintf (_T("Producer and consumer threads have terminated\n"));
_tprintf (_T("Messages produced: %d, Consumed: %d, Known Lost: %d\n"),
mblock.sequence, mblock.nCons, mblock.nLost);
return 0;
}
DWORD WINAPI produce (void *arg)
/* Producer thread - Create new messages at random intervals */
{
srand ((DWORD)time(NULL)); /* Seed the random # generator */
while (!mblock.f_stop) {
/* Random Delay */
Sleep(rand()/100);
/* Get the buffer, fill it */
EnterCriticalSection (&mblock.mguard);
__try {
if (!mblock.f_stop) {
mblock.f_ready = 0;
MessageFill (&mblock);
mblock.f_ready = 1;
mblock.sequence++;
}
}
__finally { LeaveCriticalSection (&mblock.mguard); }
}
return 0;
}
DWORD WINAPI consume (void *arg)
{
DWORD ShutDown = 0;
CHAR command, extra;
/* Consume the NEXT message when prompted by the user */
while (!ShutDown) { /* This is the only thread accessing stdin, stdout */
_tprintf (_T("\n**Enter 'c' for consume; 's' to stop: "));
_tscanf ("%c%c", &command, &extra);
if (command == 's') {
EnterCriticalSection (&mblock.mguard);
ShutDown = mblock.f_stop = 1;
LeaveCriticalSection (&mblock.mguard);
} else if (command == 'c') { /* Get a new buffer to consume */
EnterCriticalSection (&mblock.mguard);
__try {
if (mblock.f_ready == 0)
_tprintf (_T("No new messages. Try again later\n"));
else {
MessageDisplay (&mblock);
mblock.nCons++;
mblock.nLost = mblock.sequence - mblock.nCons;
mblock.f_ready = 0; /* No new messages are ready */
}
}
__finally { LeaveCriticalSection (&mblock.mguard); }
} else {
_tprintf (_T("Illegal command. Try again.\n"));
}
}
return 0;
}
void MessageFill (MSG_BLOCK *mblock)
{
/* Fill the message buffer, and include checksum and timestamp */
/* This function is called from the producer thread while it */
/* owns the message block mutex */
DWORD i;
mblock->checksum = 0;
for (i = 0; i < DATA_SIZE; i++) {
mblock->data[i] = rand();
mblock->checksum ^= mblock->data[i];
}
mblock->timestamp = time(NULL);
return;
}
void MessageDisplay (MSG_BLOCK *mblock)
{
/* Display message buffer, timestamp, and validate checksum */
/* This function is called from the consumer thread while it */
/* owns the message block mutex */
DWORD i, tcheck = 0;
for (i = 0; i < DATA_SIZE; i++)
tcheck ^= mblock->data[i];
_tprintf (_T("\nMessage number %d generated at: %s"),
mblock->sequence, _tctime (&(mblock->timestamp)));
_tprintf (_T("First and last entries: %x %x\n"),
mblock->data[0], mblock->data[DATA_SIZE-1]);
if (tcheck == mblock->checksum)
_tprintf (_T("GOOD ->Checksum was validated.\n"));
else
_tprintf (_T("BAD ->Checksum failed. message was corrupted\n"));
return;
}
2. Відповідно до варіанту (таблиця) модифікувати програму так, щоб замінити обєкти синхронізації заданого числа потоків
Таблиця Варіанти завдань
Системний |
Варіант |
|||||||||
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
|
Critical Section |
2 |
2 |
3 |
3 |
4 |
2 |
2 |
3 |
3 |
4 |
Mutex |
2 |
3 |
2 |
4 |
3 |
|||||
Semaphores |
2 |
3 |
2 |
4 |
3 |
3. Проаналізувати та пояснити вміст дисплею після завершення програми.
А также другие работы, которые могут Вас заинтересовать | |||
9444. | Бронхиальная астма | 29.68 KB | |
Бронхиальная астма БА - заболевание, в основе которого лежит хроническое воспаление дыхательных путей, сопровождающееся изменением чувствительности и реактивности бронхов и проявляющееся приступами удушья вследствие спазма бронхов, отека их сли... | |||
9445. | Ревматизм. Ревматический эндокардит. Ревматический перикардит... | 31.89 KB | |
Ревматизм Ревматизм - это системное, воспалительное заболевание соединительной ткани, с преимущественной локализацией в ССС, развивающееся в связи с инфицированием гемолитичсеким стрептококком группы А у предрасположенных лиц, преимущественно... | |||
9446. | Митральные пороки сердца | 22.99 KB | |
Митральные пороки сердца Классификация: Недостаточность митрального клапана Митральный стеноз Митральный порок с преобладанием недостаточности Митральный порок с преобладанием стеноза Митральный порок без четкого преобл... | |||
9447. | Инфекционный (септический) эндокардит | 28.47 KB | |
Инфекционный (септический) эндокардит Это заболевание при котором инфекционный очаг локализуется на клапанах сердца или пристеночном эндокарде, откуда инфекция распространяется (в результате развития бактериемии) по всему организму. При отсутствии а... | |||
9448. | Артериальные гипертензии | 31.99 KB | |
Артериальные гипертензии Это патологическое состояние при котором систолическое АД составляет 140мм.рт.ст и выше и/или диастолическое АД 90мм.рт.ст и выше при том условии, что эти значения получены в результате как минимум трех измерений, произведен... | |||
9449. | Ишемическая болезнь сердца | 19.31 KB | |
Ишемическая болезнь сердца Это обусловленное расстройством коронарного кровообращения поражение миокарда, возникающее в результате нарушения равновесия между коронарным кровотоком и метаболическими потребностями сердечной мышцы, в следствие атероскл... | |||
9450. | Недостаточность кровообращения | 28.65 KB | |
Недостаточность кровообращения Это патологическое состояние при котором ССС не способна доставлять необходимое количество крови к органам и системам. Различают: Сердечная недостаточность - это патологическое состояние обусловленное недост... | |||
9451. | Заболевания печени. Гепатиты и циррозы. Хронический гепатит | 28.47 KB | |
Заболевания печени. Гепатиты и циррозы. Хронический гепатит. Хронический гепатит - группа заболеваний печени, вызываемых различными причинами, характеризующихся различной степенью выраженности печеночно-клеточного некроза и воспаления про... | |||
9452. | ПОНЯТИЕ, ПРЕДМЕТ, СФЕРА ДЕЙСТВИЯ И МЕТОД ТРУДОВОГО | 60 KB | |
ТЕМА№1: ПОНЯТИЕ, ПРЕДМЕТ, СФЕРА ДЕЙСТВИЯ И МЕТОД ТРУДОВОГО ПРАВА. ПОНЯТИЕ ТРУДОВОГО ПРАВА РОССИИ, ЦЕЛИ И ЗАДАЧИ ТП. предмет ТП СФЕРА ДЕЙСТВИЯ ТП Метод трудового права: понятие и основные особенности правового регулирования тр... | |||