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

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

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

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

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

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

МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ НАЦІОНАЛЬНИЙ УНІВЕРСИТЕТ "ЛЬВІВСЬКА ПОЛІТЕХНІКА" ІНСТИТУТ ПІСЛЯДИПЛОМНОЇ ОСВІТИ КАФЕДРА ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ / ЗВІТ ДО ЛАБОРАТОРНОЇ РОБОТИ №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 Слід зазначити, що, запускаючи дану програму багаторазово, можна отримати інші результати , що відрізняються загальною кількістю циклів перевірки і тривалістю роботи окремих потоків. Це пояснюється різним ступенем завантаження процесора в момент запуску програми (тобто наявністю в даний момент готових до виконання потоків інших процесів, що існують паралельно) .
Антиботан аватар за замовчуванням

11.12.2017 10:47

Коментарі

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

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

Нічого не вибрано
0%

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

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

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

Admin

26.02.2023 12:38

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