- •Методичні вказівки
- •Лабораторна робота №1 Передача та прийом даних через com-порт.
- •1.1. Мета роботи
- •1.2. Теоретичні відомості
- •1.3. Програма роботи
- •1.4. Обладнання та програмне забезпечення
- •1.5. Порядок виконання роботи
- •1.6. Контрольні запитання.
- •Лабораторна робота №2 Реалізація потоку та синхронізація даних, настроювання пріоритетів завдань на Builder (Delphi)
- •2.1. Мета роботи
- •2.2. Теоретичні відомості
- •2.3. Програма роботи
- •2.4. Обладнання та програмне забезпечення
- •2.5. Порядок виконання роботи і опрацювання результатів Варіант 1 (Порядок виконання лабораторної роботи
- •Варіант 2 (Порядок виконання лабораторної роботи на мові програмування Delphi)
- •2.5. Контрольні запитання.
- •Література:
1.6. Контрольні запитання.
1.6.1. Що таке Com-port?
1.6.2. Як можна ще назвати Com-port?
1.6.3. На яку максимальну відстань можна передати дані
використовуючи Com-port (описати всі варіанти)?
1.6.4. Описати властивості Com-portу.
1.6.5. Які функції використовують для програмування портів.
1.6.6. Функція для ініціалізації порту (описати).
1.6.7. Функція для закриття порту (описати).
1.6.8. Функція для запису даних в порт (описати).
1.6.9. Функція для зчитування даних з порту (описати).
Лабораторна робота №2 Реалізація потоку та синхронізація даних, настроювання пріоритетів завдань на Builder (Delphi)
2.1. Мета роботи
Ознайомитись з принципом створення потоку та синхронізації даних. Створити проект для отримання потоку даних.
2.2. Теоретичні відомості
Потоки дозволяють у рамках однієї програми вирішувати кілька завдань одночасно. Ця можливість є у всіх операційних системах для персональних комп'ютерів починаючи з появи Windows NT.
Операційна система (ОС) надає додатку деякий інтервал часу центрального процесора (ЦП) і в момент, коли додаток переходить до очікування повідомлень або звільняє процесор, операційна система передає керування іншому завданню. Щоб використовувати всі переваги, забезпечувані декількома процесорами в сучасних операційних системах, програміст повинен знати, як створювати потоки.
Огляд потоків
Потоки — це набори команд, які можуть одержувати час процесора. Час процесора виділяється квантами. Квант часу - це мінімальний інтервал, протягом якого тільки один потік використовує процесор.
Зверніть увагу, що кванти виділяються не програмам або процесам, а саме породженим ними потокам. Як мінімум, кожний процес має хоча б один (головний) потік, але операційні системи, починаючи з Windows 95 і Windows NT дозволяють запустити в рамках процесу довільне число потоків.
Існують дві моделі застосування потоків - асиметрична й симетрична.
У рамках асиметричної моделі потоки вирішують різні завдання і, як правило, не розділяють спільні ресурси. Один з таких потоків відповідає за друк; інший обробляє повідомлення від клавіатури й миші; третій керує автоматичним збереженням документа користувача.
У симетричній моделі потоки виконують ту саму роботу, розділяють одні ресурси й виконують один код. Приклад додатка із симетричними потоками - практично будь-яка велика клієнт/сервернаСКБД. Для обслуговування кожної транзакції запускається, як правило, окремий новий потік. До додатка можна додавати нові симетричні потоки в міру зростання навантаження (числа запитів).
Потоки й процеси
Коли ми говоримо "програма", то звичайно маємо на увазі поняття, у термінології операційної системи позначуване як "процес". Процес складається з віртуальної пам'яті, що виконується коду, потоків і даних. Процес може містити багато потоків, але обов'язково містить принаймні один. Потік, як правило, має "у власності" мінімум ресурсів; він залежить від процесу, що і розпоряджається віртуальною пам'яттю, кодом, даними, файлами й іншими ресурсами ОС.
Чому ми використовуємо потоки замість процесів, хоча, при необхідності, додаток може складатися й з декількох процесів? Справа в тому, що перемикання між процесами - значно більше тривала операція, чим перемикання між потоками. Інший довід на користь застосування потоків - те, що вони спеціально задумані для спільних використань ресурсів; розділити ресурси між процесами (які мають розподілений адресний простір) не так-то просто. Ще одна із суттєвих переваг використання багато поточних програм – використання на машинах з багатоядерним центральним процесором так як це рух вперед паралельно технічному процесу.
Фонові процедури
Раніше програмісти намагалися емулювати потоки, запускаючи процедури усередині циклу обробки повідомлень. Цикл обробки повідомлень, або цикл очікування - це особливий фрагмент коду в програмі, керованої подіями. Він виконується тоді, коли програма знаходить у черзі події, які потрібно обробити; якщо таких немає, програма може виконати в цей час "фонову процедуру". Такий спосіб імітації потоків досить складний, тому що змушує програміста, по-перше, зберігати контекст фонової процедури, а по-друге, визначати момент, коли вона поверне керування оброблювачеві подій. Якщо така процедура виконується довго, то в користувача може склались враження, що додаток перестав реагувати на зовнішні події. Використання потоків знімає проблему перемикання контексту, тепер контекст (стек і регістри) зберігає операційна система.
В Delphi можливість створити фонову процедуру реалізована через подію Onldle об'єкта Application:
property Onldle: TIdleEvent;
type TIdleEvent = procedure (Sender: TObject; var Done: Boolean) of object;
Якщо завдання додатка можна розділити на різні підмножини: обробка подій, введення/виведення, зв'язок і інші; то потоки можуть бути органічно вбудовані в програмне рішення. Якщо розробник розможе розділити більше завдання на декілька дрібних, це тільки підвищить переносимість коду і можливості його багаторазового використання.
Зробивши додаток багатопоточним, програміст отримує додаткові можливості керування ним. Наприклад, через керування пріоритетами потоків. Якщо один з них "пригальмовує" додаток, займаючи багато процесорного часу, його пріоритет може бути знижений.
Інша важлива перевага впровадження потоків - при зростанні "навантаження" на додаток можна збільшити кількість потоків і тим самим зняти проблему.
Типові помилки при використанні потоків
Як і при використанні інших сильнодіючих засобів, відносно потоків ви повинні дотримувати певних правил безпеки. Дві типові проблеми, з якими програміст може зштовхнутися при роботі з потоками — це перегони (race conditions) і тупики (deadlocks).
Перегони
Ситуація перегонів виникає, коли два або більше потоки намагаються одержати доступ до загального ресурсу й змінити його стан. Розглянемо наступний приклад. Нехай Потік 1 одержав доступ до ресурсу й змінив його у своїх інтересах; потім активізувався Потік 2 і модифікував цей же ресурс до завершення Потоку 1. Потік 1 думає, що ресурс залишився у тому ж стані, у якому був до перемикання. Залежно від того, коли саме був змінений ресурс, результати можуть варіюватися - іноді код буде виконуватися коректно, іноді ні. Програмісти не повинні будувати ніяких гіпотез щодо порядку виконання потоків, тому що планувальник ОС може запускати й зупиняти їх у будь-який час.
Тупики
Тупики мають місце, коли потік очікує ресурс, що у цей момент належить іншому потоку. Розглянемо приклад: Потік 1 захоплює об'єкт А і, для того щоб продовжувати роботу, чекає можливості захопити об'єкт Б. У той же час Потік 2 захоплює об'єкт Б і чекає можливості захопити об'єкт А. Розвиток цього сценарію заблокує обидва потоки; жоден з них не буде виконуватися.
Ситуації з перегонами, так і тупиками можна уникнути, якщо використовувати прийоми, які ми розглянемо нижче (Засобу синхронізації потоків).
Пріоритети потоків
Інтерфейс Win 32 API дозволяє програмістові управляти розподілом часу між потоками; це поширюється й на додатки, написані на Builder. Операційна система планує час процесора відповідно до пріоритетів потоків. Коли потік створюється, йому призначається пріоритет, що відповідає пріоритету того процесу, що його створив. У свою чергу, процеси можуть мати наступні класи пріоритетів.
Реального часу (Real time)
Високий (High)
Нормальний (Normal)
Фоновий (Idle)
Клас реального часу - визначає пріоритет навіть більший, ніж у багатьох процесів операційної системи. Такий пріоритет потрібний для процесів, що обробляють високошвидкісні потоки даних. Якщо такий процес не завершиться за короткий час, У користувача складеться враження, що система перестала відгукуватися, тому що навіть обробка подій миші не одержить часу процесора.
Клас із високим пріоритетом використовується досить рідко. Його використання обмежене процесами, які повинні завершуватися за короткий час, щоб не викликати збійної ситуації. Приклад - процес, що посилає сигнали зовнішньому пристрою; причому пристрій відключається, якщо не одержить своєчасний сигнал.
Більшість процесів запускається в рамках класу з нормальним пріоритетом. Нормальний пріоритет означає, що процес не вимагає якої-небудь спеціальної уваги з боку операційної системи.
Процеси з фоновим пріоритетом запускаються лише в тому випадку, якщо в черзі Диспетчера завдань немає інших процесів. Звичайні види додатків, що використовують такий пріоритет, — це програми-заставки й системних агентів (system agents). Програмісти можуть використовувати фонові процеси для організації завершальних операцій і реорганізації даних. Прикладами можуть служити автозбереження документа або бази даних.
Пріоритети мають чисельні значення від 0 до 31. Процес, що створив потік, може згодом змінити його пріоритет; у цій ситуації програміст має можливість управляти швидкістю відгуку кожного потоку.
Пріоритет потоку може відрізнятися від пріоритету його процесу, що породив, на плюс-мінус дві одиниці. Відповідні величини показані в табл. 1.
Таблиця 1. Класи процесів і пріоритети їхніх потоків
|
Нижчий |
Знижений |
Нормальний |
Підвищений |
Вищий |
Фоновий |
2 |
3 |
4 |
5 |
6 |
Нормальний заднього плану |
5 |
6 |
7 |
8 |
9 |
Нормальний переднього плану |
7 |
8 |
9 |
10 |
11 |
Високий |
11 |
12 |
13 |
14 |
15 |
Реального часу |
22 |
23 |
24 |
25 |
26 |
Як видно з таблиці, у програміста є широкий діапазон можливостей по керуванню швидкістю відгуку. Потоки для обробки користувальницьких подій (миша, клавіатура) можуть бути запущені з нормальним пріоритетом і продовжувати реагувати на події при наявності декількох потоків, що працюють зі зниженим пріоритетом.
Клас TThread
Builder надає програмістові повний доступ до можливостей програмування інтерфейсу Win32. Для чого ж тоді фірма Inprise представила спеціальний клас для організації потоків? Клас повинен спрощувати програмний інтерфейс; клас TThread - прекрасний приклад надання розроблювачеві простого доступу до програмування потоків. Інша відмінна риса класу TThread - це гарантія сумісності з бібліотекою візуальних компонентів VCL. Без використання класу TThread під час викликів VCL можуть виникнути ситуації перегонів.
Потрібно усвідомлювати, що з погляду операційної системи, потік - це її об'єкт. При створенні він отримує дескриптор і відслідковується ОС. Об'єкт класу TThread - це конструкція Builder, що відповідає потоку ОС. Цей об'єкт VCL створюється до реального виникнення потоку в системі й знищується після його зникнення.
Вивчення класу TThread почнемо з конструктора:
Лістинг на Delphi:
constructor Create(CreateSuspended: Boolean);
Лістинг на Builder:
__fastcall TThread(bool CreateSuspended);
Як аргумент він одержує параметр CreateSuspended. Якщо його значення дорівнює true, знову створений потік не починає виконуватися доти, поки не буде зроблений виклик методу Resume. У випадку, якщо CreateSuspended має значення false, потік починає виконання й конструктор завершується.
Лістинг на Delphi:
destructor Destroy; override;
Лістинг на Builder:
__fastcall virtual ~TThread(void);
Деструктор ~TThread(void) викликається, коли необхідність у створеному потоці відпадає. Деструктор завершує його й вивільняє всі ресурси, зв'язані з об'єктом TThread.
Лістинг на Delphi:
procedure Resume;
Лістинг на Builder:
void __fastcall Resume(void);
Метод Resume класу TThread викликається, коли потік відновляється після зупинки, або якщо він був створений з параметром CreateSuspended рівним true.
Лістинг на Delphi:
procedure Suspend;
Лістинг на Builder:
void __fastcall Suspend(void);
Виклик методу suspend припиняє потік з можливістю повторного запуску згодом. Метод suspend припиняє потік поза залежністю від коду, що виконується потоком у цей момент; виконання триває.
Лістинг на Delphi:
property Suspended: Boolean;
Лістинг на Builder:
__property bool Suspended;
Властивість suspended дозволяє програмістові визначити, чи не припинений потік. За допомогою цієї властивості можна також запускати й зупиняти потік. Установивши suspended в true, ви одержите той же результат, що й при виклику методу Suspend - припинення. Навпаки, установка Suspended в false відновляє виконання потоку, як і виклик методу Resume.
Лістинг на Delphi:
function Terminate: Integer;
Лістинг на Builder:
void __fastcall Terminate(void);
Для остаточного завершення потоку (без наступного запуску) існує метод Terminate; він зупиняє потік і повертає керування процесу, що викликав, тільки після того, як це відбулося. Значення, що повертається функцією Terminate, відповідає стану потоку. Прикладами можливих станів є випадок нормального завершення й випадок, коли до моменту виклику Terminate потік уже завершився (або був завершений з іншого потоку).
Лістинг на Delphi:
property Terminated: Boolean;
Лістинг на Builder:
__property bool Terminated
Властивість Terminated дозволяє визначити, чи відбувся вже виклик методу Terminate чи ні.
Лістинг на Delphi:
function WaitFor: Integer;
Лістинг на Builder:
unsigned __fastcall WaitFor(void);
Метод WaitFor призначений для синхронізації й дозволяє одному потоку дочекатися моменту, коли завершиться інший потік. Якщо ви усередині потоку під ім'ям FirstThread пишете код:
Лістинг на Delphi:
Code := SecondThread.WaitFor;
Лістинг на Builder:
Code= SecondThread->WaitFor()
то це означає, що потік FirstThread зупиняється до моменту завершення потоку SecondThread. Метод WaitFor повертає код завершення очікуваного потоку.
Лістинг на Delphi:
Property Handle: THandle read FHandle;
Property ThreadID: THandle read FThreadID;
Лістинг на Builder:
__property unsigned Handle = {read=FHandle, nodefault};
__property unsigned ThreadID = {read=FThreadID, nodefault};
Властивості Handle і ThreadID дають програмістові безпосередній доступ до потоку засобами Win 32 API. Якщо розробник хоче звернутися до потоку й управляти їм, минаючи можливості класу TThread, значення Handle і ThreadID можуть бути використані як аргументи функцій Win 32 API. Наприклад, якщо програміст хоче перед продовженням виконання додатка дочекатися завершення відразу декількох потоків, він повинен викликати функцію API WaitForMultipleObjects; для її виклику необхідний масив дескрипторів потоків.
Лістинг на Delphi:
property Priority: TThreadPriority;
Лістинг на Builder:
__property TThreadPriority Priority;
Властивість Priority дозволяє запросити й встановити пріоритет потоків. Пріоритет визначає, наскільки часто потік одержує час процесора. Природно, програміст захоче виділити головному потоку в додатку більший час, а потоку, наприклад, з фоновою перевіркою орфографії - менше. Припустимими значеннями пріоритету є tpldie, tpLowest,
Лістинг на Delphi:
procedure Synchronize(Method: TThreadMethod);
Лістинг на Builder:
void __fastcall Synchronize(TThreadMethod Method);
Цей метод ставиться до секції protected, тобто може бути викликаний тільки з нащадків TThread. Builder надає програмістові метод Synchronize для безпечного виклику методів VCL усередині потоків. Щоб уникнути ситуацій перегонів, метод Synchronize дає гарантію, що до кожного об'єкта VCL одночасно має доступ тільки один потік. Аргумент, переданий у метод Synchronize, - це ім'я методу, що робить звертання до VCL; виклик Synchronize із цим параметром - це те ж, що й виклик самого методу. Такий метод (класу TThreadMethod) не повинен мати ніяких параметрів і не повинен повертати ніяких значень.
Перевизначаючи метод Execute, ми можемо тим самим закладати в новий потоковий клас те, що буде виконуватися при його запуску. Якщо потік був створений з аргументом СreateSuspended, рівним false, то метод Execute виконується негайно, у противному випадку Execute виконується після виклику методу Resume.
Якщо потік розрахований на однократне виконання яких-небудь дій, то ніякого спеціального коду завершення для нього писати не треба. Після виконання методу Execute буде викликаний деструктор, що зробить все необхідне.
Якщо ж у потоці буде виконуватися якийсь цикл, і потік повинен завершитися разом з додатком, то умови закінчення циклу повинні бути приблизно такими:
Лістинг на Delphi:
procedure TMyThread.Execute;
begin repeat DoSomething;
Until Cancel-Condition or Terminated;
end;
Лістинг на Builder:
void __fastcall TMyThread::Execute()
{
for( ; !(Cancel-Condition or Terminated); )
{
// repeat DoSomething
}
}
Тут Cancel-Condition - ваша особиста умова завершення потоку (вичерпання даних, надходження на вхід того або іншого символу й т.п.), а властивість Terminated говорить про завершення потоку ззовні (швидше за все, завершується його процес, що породив).
Із завершенням потоку треба бути дуже уважним - якщо він зациклився й не реагує на сигнали завершення, то зависне весь додаток.
Лістинг на Delphi:
Property ReturnValue: Integer;
Лістинг на Builder:
__property int ReturnValue;
Властивість ReturnValue дозволяє довідатися й встановити значення, що повертається потоком при його завершенні. Ця величина повністю визначається користувачем. По замовчуванням потік повертає нуль, але якщо програміст захоче повернути іншу величину, то проста переустановка властивості ReturnValue усередині потоку дозволить одержати цю інформацію іншим потокам.
