Робота з потоками в ОС Windows

Інформація про навчальний заклад

ВУЗ:
Національний університет Львівська політехніка
Інститут:
Інститут післядипломної освіти
Факультет:
Не вказано
Кафедра:
Кафедра програмного забезпечення

Інформація про роботу

Рік:
2024
Тип роботи:
Звіт до лабораторної роботи
Предмет:
Інші

Частина тексту файла (без зображень, графіків і формул):

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ "ЛЬВІВСЬКА ПОЛІТЕХНІКА" ІНСТИТУТ ПІСЛЯДИПЛОМНОЇ ОСВІТИ КАФЕДРА ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ / ЗВІТ ДО ЛАБОРАТОРНОЇ РОБОТИ №2 на тему: "Робота з потоками в ОС Windows" Мета роботи: Ознайомитися з багатопоточністю в ОС Windows. Навчитися працювати з потока-ми, використовуючи WinAPI-функції. КОРОТКІ ТЕОРЕТИЧНІ ВІДОМОСТІ Потоком (або завданням) називається фрагмент коду програми, який може виконуватися процесором автономно і незалежно від інших частин коду цього ж додатка, але в рамках одного процесу. Як правило , код потоку представляється в програмі у вигляді окремої процедури (функції). Процесорний час надається кожному потоку незалежно від інших потоків даного процесу і потоків інших процесів . Коли один з потоків процесу блокується (наприклад , для очікування завершення операції вводу-виводу) , активізується інший потік . Таким чином , роздільне виконання потоків дає можливість скористатися перевагами мультипрограммной обробки , і в багатьох випадках скоротити загальний час виконання процесу за рахунок скорочення простоїв процесора. Потік (thread) в ОС Windows є основним елементом виконання будь-якої програми . Процес Windows може містити кілька незалежних потоків, які поділяють загальний адресний простір та інші ресурси процесу (у простому випадку додаток складається з одного єдиного потоку). Водночас потік є самостійним елементом виконання всередині процесу . Кожен з потоків може мати власний стек і свій рівень пріоритету. Як відомо , створення процесу в ОС Windows проводиться за допомогою системної функції CreateProcess . Виконання цієї функції призводить до породження потоку , який називають головним потоком процесу (T0 , рис. 1) . Головний потік присутній в будь-якому процесі і часто залишається єдиним . Решта потоки (T1 , T2 і т.д.) можуть створюватися в коді головного потоку по розсуду програміста за допомогою спеціальної функції WIN32 API CreateThread . Потік , який виконав функцію CreateThread називають батьківським потоком по відношенню до створеного ним , а створений потік - дочірнім потоком. Функція CreateThread повертає значення дескриптора створеного потоку. Це значення використовується потім для управління створеним потоком з боку батьківського потоку .    Рис.1 Функції управління процесами і потоками У Win32 API пропонується цілий ряд функцій для роботи з потоками , включаючи : • CreateThread - створити і запустити потік ; • ExitThread - завершити роботу і знищити потік з ініціативи самого потоку ; • TerminateThread - завершити роботу і знищити дочірній потік ; • SuspendThread - призупинити ( блокувати ) виконання дочірнього потоку ( потік не отримує квантів процесора ) ; • ResumeThread - відновити виконання раніше припиненого дочірнього потоку ( деблокування ) ; • Sleep - призупинити виконання потоку на заданий час ; • SetThreadPriority - змінити пріоритет дочірнього потоку ; • GetThreadPriority - визначити пріоритет дочірнього потоку ; • GetExitCodeThread - отримати код завершення дочірнього потоку ; • WaitForMultipleObjects - призупинити ( блокувати ) виконання батьківського потоку до завершення одного або декількох дочірніх потоків . Зверніть увагу , що більша частина функцій доступна тільки з батьківського потоку по відношенню до породженому їм дочірньому і тільки три з них ( CreateThread , ExitThread і Sleep ) можуть бути викликані безпосередньо з дочірнього потоку . Детальний опис найбільш важливих функцій представлено в п.2. Слід зазначити , що створення багатопоточних програм часто пов'язано з необхідністю координації робіт , виконуваних окремими потоками , особливо , якщо потоки використовують загальні ( що розділяються ) ресурси (наприклад , змінні або файли). У цьому випадку , щоб уникнути помилок у значеннях поділюваних даних , необхідно використовувати спеціальні системні засоби взаимоисключения і синхронізації потоків ( критичні секції , семафори , м'ютекси тощо). Ці засоби дозволяють описати регламент , відповідно до якого потоки зможуть отримувати доступ до загальних ресурсів , дотримуючись принцип черговості . У даній роботі методи синхронізації потоків не застосовуються (їх вивченню присвячена наступна робота). Однак , при виконанні робочого завдання ( п.4) буде можливість переконатися , до чого призводить ігнорування цього важливого питання. ФУНКЦІЇ WIN32 API ДЛЯ РОБОТИ З ПОТОКАМИ Cтворення потоку HANDLE CreateThread (   LPSECURITY_ATTRIBUTES lpThreadAttributes , / / ​​атрибути   / / захисту   DWORD dwStackSize , / / ​​розмір стека   LPTHREAD_START_ROUTINE lpStartAddress , / / ​​функція потоку   LPVOID lpThreadParameter , / / ​​параметр функції потоку   DWORD dwCreationFlags , / / ​​параметр запуску потоку   LPDWORD lpThreadID ) ; / / ідентифікатор потоку Ця функція створює дочірній потік , встановлює його характеристики (атрибути захисту, розмір стека) ​​, вказує на код функції потоку і залежно від значення параметра dwCreationFlags або виробляє його запуск , або зупиняє до спеціального розпорядження . Під функцією потоку розуміють описану в програмі функцію , яка містить код , виконання якого має здійснюватися в рамках даного потоку . Параметри ( in - вхідні , out - вихідні ) : lpThreadAttributes - покажчик на структуру SECURITY_ATTRIBUTES , визначальну атрибути захисту для створюваного потоку (in). Рекомендується задавати значення NULL , яке дозволяє використовувати будь-які функції для управління даними потоком. dwStackSize - розмір стека потоку в байтах (in) . Для використання розміру стека батьківського потоку використовуйте значення 0. lpStartAddress - покажчик на функцію програми , яку виконуватиме потік (можна задати просто ім'я цієї функції ) ( in ) . Функція потоку приймає як параметр єдиний 32 - розрядний параметр і повертає код завершення типу DWORD . Потік може представити цей параметр як значення типу DWORD або як покажчик . Наприклад , опис функції потоку може виглядати так: DWORD ThreadFunc ( LPVOID ) lpThreadParameter - покажчик , який передається потоку як параметр і зазвичай інтерпретується їм , як покажчик на деяку структуру ( in ) . За відсутності параметрів слід вказати NULL. dwCreationFlags - параметр запуску потоку ( in ) . Нульове значення параметра означає , що потік готовий до негайного виконання . Якщо як значення цього параметра вказати константу CREATE_SUSPENDED , то новий потік буде перебувати в стані очікування до тих пір , поки не буде викликана функція ResumeThread . lpIDThread - покажчик на змінну типу DWORD , в яку буде поміщений ідентифікатор ( системний номер ) створеного потоку ( out ) . Значення, що повертається : у разі успіху функція CreateThread повертає дескриптор створеного потоку ( тип handle ) , який необхідний для виконання різних операцій над потоком. При помилку функція повертає значення NULL. Завершення потоку Потік може завершитися за власною ініціативою або за ініціативою батьківського потоку , формуючи при цьому код завершення . У першому випадку потік завершується при виконанні оператора повернення з функції потоку ( return ) або за допомогою функції ExitThread : VOID ExitThread (    DWORD dwExitCode ) ; / / код завершення потоку В якості єдиного параметра цієї функції задається код завершення потоку . У другому випадку застосовується функція TerminateThread , за допомогою якої батьківський потік може примусово завершити виконання свого дочірнього потоку : BOOL TerminateThread (  HANDLE hThread , / / ​​дескриптор потоку  DWORD dwExitCode ) ; / / код завершення потоку Значення дескриптора потоку визначається за значенням , що повертається функцією CreateThread при створенні потоку . Відзначимо також , що всі потоки , створені в рамках якого процесу, автоматично завершують своє виконання при завершенні роботи процесу (тобто виконанні функції ExitProcess ) . При цьому звільняються всі значення дескрипторів потоків . Перевірка стану потоку Для отримання коду завершення раніше запущеного дочірнього потоку використовується функція GetExitCodeThread : BOOL GetExitCodeThread (  HANDLE hThread , / / ​​дескриптор потоку  LPDWORD lpdwExitCode ) ; / / адреса для прийому коду      / / Завершення Якщо потік , для якого викликана дана функція , все ще працює , замість коду завершення повертається значення STILL_ACTIVE . Ця функція може бути використана для циклічної перевірки факту завершення роботи дочірнього потоку ( див. приклад у п.3) . Очікування завершення виконання потоку Для перекладу батьківського потоку в режим очікування ( блокування ) до моменту завершення декількох запущених їм потоків , доцільно використовувати функцію WaitForMultipleObjects : DWORD WaitForMultipleObjects ( DWORD cObjects , / / ​​кількість очікуваних потоків CONST HANDLE * lphObjects , / / ​​адреса масиву  / / дескрипторів потоків BOOL fWaitAll , / / ​​тип очікування DWORD dwTimeout ) ; / / час очікування в мс Наприклад , якщо запущено три потоку і їх дескриптори представлені у вигляді масиву HANDLE hThread [ 3 ] , то очікування до тих пір , поки всі три потоки не завершаться , можна організувати таким чином:  WaitForMultipleObjects ( 3 , hThreads , TRUE , INFINITE ) ; Тип очікування TRUE означає очікування завершення всіх потоків ( FALSE - хоча б одного з потоків). Час очікування INFINITE означає нескінченне очікування до настання необхідного події. Диспетчеризація і керування пріоритетами потоків В ОС Windows використовується принцип пріоритетною диспетчеризації потоків . Це означає , що кванти процесорного часу частіше виділяються потокам з більш високим пріоритетом. Значення пріоритету встановлюються в діапазоні від 1 до 31 ( 31 відповідає максимальному пріоритету ) . Існують 4 рівня ( класу) пріоритетів , які призначаються процесам при їх створенні (залежно від типу процесу) : IDLE_PRIORITY_CLASS = 4 - низькопріоритетні процеси ; NORMAL_PRIORITY_CLASS = 9 - звичайні процеси ; HIGH_PRIORITY_CLASS = 13 - високопріоритетні процеси ; REALTIME_PRIORITY_CLASS = 24 - процеси реального часу; Потоки спочатку отримують таке ж значення пріоритету , як і у процесу . Звичайні користувача процеси (і їх потоки) за замовчуванням отримують значення пріоритету 9 , що відповідає класу NORMAL_PRIORITY_CLASS . За допомогою функції SetThreadPriority можна змінити відносний пріоритет потоку , але тільки в рамках установленого класу : BOOL SetThreadPriority ( HANDLE hThread , / / ​​дескриптор потоку int nPriority ) ;/ / новий рівень пріоритету потоку Новий рівень пріоритету потоку задається за допомогою спеціальних констант, які встановлюють величину зміни пріоритету потоку щодо пріоритету процесу : THREAD_PRIORITY_ABOVE_NORMAL +1 THREAD_PRIORITY_HIGHEST +2 THREAD_PRIORITY_NORMAL 0 THREAD_PRIORITY_BELOW_NORMAL -1 THREAD_PRIORITY_LOWEST -2 THREAD_PRIORITY_TIME_CRITICAL = 15 (або = 31 ) Остання із зазначених констант THREAD_PRIORITY_TIME_CRITICAL дозволяє встановити абсолютне значення пріоритету потоку, рівне 31 для процесів класу REALTIME_PRIORITY_CLASS або 15 для решти класів . У будь-який момент часу можна визначити поточне значення пріоритету потоку c дескриптором hThread за допомогою функції Int GetThreadPriority ( HANDLE hThread ) ; Слід зазначити , що операційна система Windows може автоматично змінювати пріоритет потоків залежно від їх поточного стану : збільшувати , коли потік взаємодіє з користувачем або знижувати , коли потік переходить в стан очікування . Приклад багатопотокової програми Розглянемо приклад консольної багатопотокової програми , яка створює два дочірніх потоку, виконують рахункові цикли типу for . В якості функцій потоків використовуються функції Thread1Proc і Thread2Proc відповідно. Потоки завершуються за власною ініціативою з використанням оператора return і повертають код завершення . Головний потік після створення дочірніх потоків циклічно перевіряє їх стан за допомогою функції GetExitCodeThread , формує прапори стану потоків flag1 і flag2 і видає відповідні повідомлення ( « потік працює » або « потік завершений »). Програма завершує роботу після того , як обидва дочірніх потоку завершили своє виконання ( flag1 = flag2 = 0). / / Приклад багатопотокової програми # include <windows.h> # include <conio.h> # include <iostream.h> HANDLE hThread1 ; / / дескриптор потоку 1 HANDLE hThread2 ; / / дескриптор потоку 2 DWORD IDThread1 ; / / ідентифікатор потоку 1 DWORD IDThread2 ; / / ідентифікатор потоку 2 DWORD dwExitCode1 ; / / код завершення потоку 1 DWORD dwExitCode2 ; / / код завершення потоку 2 / / Оголошена функція потоку 1 : DWORD Thread1Proc ( HWND hwnd1 ) ; / / Оголошена функція потоку 2 : DWORD Thread2Proc ( HWND hwnd2 ) ; int main ( ) {   cout << " Багаторівнева програма " << endl ; / / Створення потоку 1   hThread1 = CreateThread (NULL , 0 , Thread1Proc , NULL , 0 , & IDThread1 ) ;   if ( hThread1 == NULL)    { Cout << " Помилка при створенні потоку 1 " << endl ;      getch ();      return 0 ;    }    else      cout << " Потік 1 створено; ID = " << IDThread1 << endl ; / / Створення потоку 2 hThread2 = CreateThread (NULL , 0 , Thread2Proc , NULL , 0 , & IDThread2 ) ;   if ( hThread2 == NULL)    { Cout << " Помилка при створенні потоку 2 " << endl ;      getch ();      return 0 ;    }    else      cout << " Потік 2 створено; ID = " << IDThread2 << endl ; int flag1 = 1 ; / / прапор стану потоку 1 ( 1 = активний) int flag2 = 1 ; / / прапор стану потоку 2 ( 1 = активний) int k = 0 ; / / лічильник циклів перевірки / / Циклічна перевірка кодів завершення потоків / / З виведенням повідомлень про їх стан while ( flag1 + flag2 )    {     k + + ;     cout << " Цикл " << k << endl ;    if ( flag1! = 0 )       {        GetExitCodeThread ( hThread1 , & dwExitCode1 ) ;        if ( dwExitCode1 == STILL_ACTIVE )          cout << " Потік 1 поки працює " << endl ;        else       { cout << " Потік 1 завершено з кодом " << dwExitCode1 << endl ;            flag1 = 0 ;           }       }     if ( flag2! = 0 )       {        GetExitCodeThread ( hThread2 , & dwExitCode2 ) ;        if ( dwExitCode2 == STILL_ACTIVE )           cout << " Потік 2 поки працює " << endl ;        else     { cout << " Потік 2 завершено з кодом " << dwExitCode2 << endl ;            flag2 = 0 ;           }       }    }   getch ();   return 0 ; } / / Функція потоку 1 DWORD Thread1Proc ( HWND hwnd1 ) {  int i ;  for ( i = 1 ; i < 1500 ; i + +);  return 8 ; } / / Функція потоку 2 DWORD Thread2Proc ( HWND hwnd2 ) {  int i ;  for ( i = 1 ; i < 400 ; i + +);  return 3 ; } В результаті виконання цієї програми в консольному вікні з'явиться, наприклад, таке: Багаторівнева програма Потік 1 створено; ID = 4294506109 Потік 2 створено; ID = 4294494273 цикл 1 Потік 1 поки працює Потік 2 поки працює цикл 2 Потік 1 поки працює Потік 2 завершено з кодом 3 цикл 3 Потік 1 завершено з кодом 3 Слід зазначити, що, запускаючи дану програму багаторазово, можна отримати інші результати , що відрізняються загальною кількістю циклів перевірки і тривалістю роботи окремих потоків. Це пояснюється різним ступенем завантаження процесора в момент запуску програми (тобто наявністю в даний момент готових до виконання потоків інших процесів, що існують паралельно) .
Антиботан аватар за замовчуванням

03.03.2014 11:03-

Коментарі

Ви не можете залишити коментар. Для цього, будь ласка, увійдіть або зареєструйтесь.

Ділись своїми роботами та отримуй миттєві бонуси!

Маєш корисні навчальні матеріали, які припадають пилом на твоєму комп'ютері? Розрахункові, лабораторні, практичні чи контрольні роботи — завантажуй їх прямо зараз і одразу отримуй бали на свій рахунок! Заархівуй всі файли в один .zip (до 100 МБ) або завантажуй кожен файл окремо. Внесок у спільноту – це легкий спосіб допомогти іншим та отримати додаткові можливості на сайті. Твої старі роботи можуть приносити тобі нові нагороди!
Нічого не вибрано
0%

Оголошення від адміністратора

Антиботан аватар за замовчуванням

Подякувати Студентському архіву довільною сумою

Admin

26.02.2023 12:38

Дякуємо, що користуєтесь нашим архівом!