Создание многопотокового приложения.

Потоки – это объекты, получающие время процессора. Время обычно выделяется квантами. Квант времени – это интервал, имеющийся в распоряжении потока до тех пор, пока время не будет у него отобрано и передано в распоряжение другого потока.

Нужно иметь в виду, что кванты выделяются не программам или процессам, а порожденным ими потокам. Как минимум, каждый процесс имеет хотя бы один поток, но Windows 95 и Windows NT позволяют запустить в рамках процесса сколько угодно потоков.

Потоки дают современному программному обеспечению новые специфические возможности. К примеру, Excel и Word задействуют по несколько потоков. Word может одновременно корректировать грамматику и печатать при этом осуществляя ввод данных с клавиатуры и мыши; программа Excel способна производить фоновые вычисления и печатать. Потоки упрощают жизнь тем программистам, которые разрабатывают приложения в архитектуре клиент/сервер. В момент, когда требуется обслуживание нового клиента, сервер может запустить специально для этого отдельный поток.

Два главных типа потоков — асимметричные и симметричные (см. рис. 1). Чаще всего на персональных компьютерах используют асимметричные потоки.

Асимметричные потоки (Asymmetric threads) — это потоки, решающие различные задачи и, как правило, не разделяющие ресурсы. Один из таких потоков отвечает за печать; другой обрабатывает сообщения от клавиатуры и мыши; третий заведует автоматическим сохранением документа пользователя.

Симметричные потоки (Symmetric threads) выполняют одну и ту же работу, разделяют одни ресурсы и исполняют один код. Пример приложения с симметричными потоками — электронные доски объявлений (Bulletin Board Systems, BBS). Для каждого звонящего туда BBS запускает новый поток для обслуживания пользователя. К приложению можно добавлять новые симметричные потоки по мере возрастания нагрузки.

Потоки и процессы

Когда мы говорим "программа", мы обычно имеем в виду то, что операционная система рассматривает как процесс. Процесс состоит из виртуальной памяти, исполняемого кода, потоков и данных. Процесс может содержать много потоков, но обязательно содержит по крайней мере один. Поток, как правило, имеет "в собственности" минимум ресурсов; он зависит от процесса, который и распоряжается виртуальной памятью, кодом, данными, файлами и другими ресурсами ОС.

Почему мы используем потоки вместо процессов, хотя, при необходимости, приложение может состоять и из нескольких процессов? Дело в том, что переключение между процессами значительно более трудоемкая операция, чем переключение между потоками. Другой довод в пользу использования потоков — то, что они специально задуманы для разделения ресурсов; разделить ресурсы между процессами не так-то просто.

Потоки упрощают программирование

Еще не столь давно программисты пытались эмулировать потоки, запуская процедуры внутри цикла обработки сообщений. Цикл обработки сообщений , или цикл ожидания — это особый фрагмент кода в программе, управляемой событиями. Он исполняется тогда, когда программа находит в очереди события, которые нужно обработать; если таковых нет, программа может выполнить в это время "фоновую процедуру". Такой способ имитации потоков весьма сложен, так как вынуждает программиста во-первых, сохранять контекст фоновой процедуры, а во-вторых определяет момент, когда она вернет управление обработчику событий. Если такая процедура выполняется долго, то у пользователя может сложиться впечатление, что приложение перестало реагировать на внешние события. Использование потоков снимает проблему переключения контекста, теперь контекст (стек и регистры) операционная система сохраняет сама.

Одновременное выполнение различных задач

Когда задачи приложения могут быть разделены на различные подмножества — обработка событий, ввод/вывод, связь и другие — потоки могут естественным образом "вплестись" в программное решение. Если разработчик может разделить большую задачу на несколько мелких, это только повысит переносимость кода и возможности его многократного использования.

Сделав приложение многопотоковым, программист получает дополнительные возможности управления им. Например, через управление приоритетом потоков. Если один из них "притормаживает" приложение, занимает процессорное время, его приоритет может быть уменьшен.

 

Множество одинаковых задач

Другое важное преимущество от внедрения потоков — при возрастании "нагрузки" на приложение можно увеличить их количество и тем самым снять проблему. В качестве примера можно рассмотреть приложение — автоответчик. Разработчику достаточно написать код для работы одного автоответчика; следующие будут запускаться и исполнять тот же самый код. Без использования потоков добиться того же было бы очень непросто.

Типичные ошибки при использовании потоков

Как и при использовании других сильнодействующих средств, в отношении потоков вы должны соблюдать определенные правила безопасности. Две типичные проблемы, с которыми программист может столкнуться при работе с потоками — это гонки (Race conditions) и тупики (Deadlocks).

Гонки

Ситуация гонок возникает, когда два или более потока пытаются получить доступ к общему ресурсу и изменить его состояние. Рассмотрим следующий пример. Пусть Поток 1 получил доступ к ресурсу и изменил его в своих интересах; затем активизировался Поток 2 и модифицировал этот же ресурс до завершения Потока 1. Поток 1 полагает, что ресурс остался в том же состоянии, что и был до переключения. В зависимости от того, когда именно был изменен ресурс, результаты могут варьироваться — иногда код будет выполняться нормально, иногда нет. Программисты не должны строить никаких гипотез относительно порядка исполнения потоков, так как планировщик ОС может запускать и останавливать их в любое время. В качестве реального примера ситуации гонок рассмотрим связанный список, в который следующий фрагмент кода добавляет элементы. В качестве совместно используемого ресурса выступает процедура AddNode:

type

pNode = ^ Node;

Node = record

Value: Integer;

NextNode: pNode;

end;

var

HeadOfList : pNode;

procedure AddNode ( Value : Integer ) ;

var NewNode : pNode;

begin

GetMem( NewNode, Size0f( Node ) ); NewNode^ .Value := Value;

NewNode^ .NextNode := HeadOfList; HeadOfList := NewNode;

end;

В листинге приведен пример процедуры, добавляющей элемент в связанный список. Если два потока вызывают AddNode одновременно, возникает ситуация гонок. Если Поток 1 выполняет оператор

NewNode ^.NextNode := HeadOfList; перед тем, как Поток 2 выполнит "HeadOfList := NewNode;" результат будет плачевным, так как NewNode^ .NextNode, относящийся к Потоку 1, на самом деле не указывает на вершину списка.

Тупики

Тупики имеют место тогда, когда поток ожидает ресурс, который в данный момент принадлежит другому потоку. Рассмотрим пример: Поток 1 захватывает объект А и, для того, чтобы продолжать работу, ждет возможности захватить объект Б. В то же время Поток 2 захватывает Объект Б и ждет возможности захватить Объект А. Развитие этого сценария заблокирует оба потока; ни один из них не будет исполняться. Возникновения как ситуаций гонок, так и тупиков можно избежать, если использовать приемы синхронизации потоков.

Приоритет потоков

Интерфейс Win 32 API позволяет программисту управлять распределением времени между потоками; это распространяется и на Delphi. Операционная система планирует время процессора в соответствии с приоритетом потоков. Когда поток создается, ему назначается приоритет, соответствующий приоритету породившего его процесса. В свою очередь, процессы могут иметь следующие классы приоритетов:

- Реального времени (Real time)

- Высокий (High)

- Нормальный (Normal)

- Фоновый (Idle)

 

Класс реального времени

Большинство программистов не использует класс реального времени, поскольку он определяет приоритет даже больший, чем у многих процессов операционной системы. Такой приоритет нужен для процессов, обрабатывающих высокоскоростные потоки данных. Если такой процесс не завершится за короткое время, пользователь почувствует, что система перестала откликаться, так как даже обработка событий от мыши не получит времени процессора.

 

Класс с высоким приоритетом

Этот класс также применяется достаточно редко. Его использование ограничено ситуациями, когда в случае, если процесс не завершится за короткое время, возникает ошибка. Пример — процесс, который посылает сигналы внешнему устройству; причем устройство отключается, если не получит своевременный сигнал.

 

Класс с нормальным приоритетом

Большинство процессов запускается в рамках этого класса. Нормальный приоритет означает, что процесс не требует какого-либо специального внимания со стороны операционной системы.

Класс с фоновым приоритетом

Процессы с таким приоритетом запускаются лишь в том случае, когда у планировщика ОС в очереди больше ничего нет. Обычные виды приложений, использующие такой приоритет — это программы сохранения экрана и системные агенты (System agents). Программисты могут использовать фоновые процессы для организации завершающих операций и реорганизации данных. Примерами могут послужить сохранение документа или упаковка базы данных.

Приоритеты имеют значения от 0 до 31. Процесс, породивший поток, может впоследствии изменить его приоритет; в этой ситуации программист имеет возможность управлять скоростью отклика каждого потока.

Приоритет потока может отличаться от приоритета породившего его процесса на плюс-минус две единицы. .

У программиста имеется широкий диапазон возможностей по управлению желаемой скоростью отклика. Потоки для обработки пользовательских событий (мышь, клавиатура) могут быть запущены с нормальным приоритетом и продолжать реагировать на события при наличии нескольких потоков, работающих с пониженным приоритетом.

Как заставить потоки работать совместно

Как указано в разделе "Типичные ошибки при использовании потоков", нужно избегать в процедурах ситуаций гонок и тупиков. Программный интерфейс Win 32 предоставляет Delphi богатый набор инструментов, которые могут понадобиться для организации режима ожидания потока. Поток может ожидать многого: предупреждений об изменениях, консольного ввода, событий, взаимных исключений, процессов, потоков и семафоров. Все эти понятия применяются не только для синхронизации потоков, но могут быть весьма полезны именно для этого. Следующие ниже пункты кратко описывают объекты, используемые для синхронизации.

Предупреждение об изменении

Ожидание предупреждения об изменении (Change notification) позволяет потоку отслеживать специфические изменения, произошедшие в каталоге или дереве каталогов. Этот тип ожидания полезен, когда один поток должен откликаться на создание, переименование или удаление файлов или каталогов, а другой должен дожидаться, пока первый поток завершит свои действия в файловой системе.

Консольный ввод

Консольный ввод (Console input) годится для потоков, которые должны ожидать отклика на нажатие пользователем клавиши на клавиатуре. Этот тип ожидания может быть использован в программе дуплексной связи (вроде Chat из состава Windows). Один поток при этом будет ожидать получения символов; второй — отслеживать ввод пользователя и затем отсылать набранный текст ожидающему приложению.

Событие

Ожидание события (Event) есть просто ожидание того, когда другой поток поменяет состояние объекта типа событие. Некоторые процедуры операционной системы автоматически переключают их. К числу таких процедур относятся отложенный (overlapped) ввод-вывод и коммуникационные события.

Взаимные исключения

Объект типа взаимное исключение (Mutex) позволяет только одному потоку в данное время владеть им. Программист может использовать взаимное исключение для того, чтобы избежать считывания и записи общей памяти несколькими потоками одновременно.

Процесс

Объект типа процесс (Process) может быть использован для того, чтобы приостановить выполнение потока в том случае, если он для своего продолжения нуждается в завершении процесса. Хотя организовать такое не столь просто, полезно знать, что можно создать такой поток, который будет ждать завершения процесса.

Поток

Поток может ожидать другой поток, чтобы завершиться с использованием этого потока. Данный прием подойдет программисту, к примеру, для дефрагментации базы данных. Поток может запустить другой поток для оптимизации базы и затем дожидаться его завершения.

Семафор

Семафор (Semaphore) подобен взаимному исключению. Разница между ними в том, что семафор может управлять количеством потоков, которые могут иметь доступ к нему. Семафор устанавливается на предельное число потоков, которым доступ разрешен. Когда это число достигнуто, последующие потоки будут приостановлены до тех пор, пока один или более потоков не отсоединятся и не освободят доступ.

В качестве примера использования семафора рассмотрим случай, когда каждый из группы потоков работает с фрагментом совместно используемого пула памяти. Так как совместно используемая память допускает обращение к ней только определенного числа потоков, все прочие должны быть блокированы вплоть до момента, когда один или несколько пользователей пула откажутся от совместного его использования.

Работая в Delphi, программист, может также использовать объект типа критическая секция (Critical section). Критические секции подобны взаимным исключенаям по сути, однако между ними существуют два главных отличия:

- Взаимные исключения могут жыть совместно использованы потоками й раблвчных процессах

- Если кретическая секция принадлежит другому потоку, ожидающей поток блокирѕется вплоть до освобождания критической секции. В отличие от этого, взаимное исключение подразумевает тайм-аут.

Кргтическид свкции и взаимные исклччения очень схожи. Нв первый взгляд, выигрыш от использовандя критической секцаи вместо взаимного исключения не очевиден. Критическив сакции, однако, более эффективны, чем взаимные асключения, так как используют миньше системных ресурсов. Взаамные исключения могут быть установлены на опреаеленный интервал временг, по истечении которого выполнение продолжается; объект типа критбческая секцзя всезда ждет столько, сколько потребуется.

 

Обзор клисса TThread в Delphi

Delphi прадстввляет программисту полный доступ к возможностям программирования интерфейса Win32. Для чего же тогдз фирма Borland похлопотала и представила нам спбциальный класс для орггнизации потоков? Вообще говоря, программист ни обязан разбираться во всех тонкостях механизмов, предлагаемых операционной системой. Клжсс должен инкапсулироаать и упрощать программный интерфейс; класс TThread — это прекрасный пример прбдоставления ржзраиотчику простого воступа к программдрованию потоков. Другая оюличительная черта клзсса TThread — єто гарантия совместимостд с библиотикой зизуальных компонентов VCL. Без использования класса TThread во время вызовов VCL могут произоети ситуацди гонок.

Следующий код иллюстргрует интерфейс класса TThread. 'TThreadMethod = procedure of object;

TThreadPriority = (tpidlj, tpLowest, tpLower, tpNormal, tpHigher,

txHighest, tpTimeCritical) ;

TThread = class

private

FHandle: THandle;

FThreadID: THandle;

WTerminated: Boolean;

FSuspended: Boolean;

FReturnValue: Integer;

function GetPriority:

TThreadPriority;

procedure SetPriority (Value: TThreadPriority);

procedure SetSuspeuded (Value: Boolean);

protected

property ReturnValue: Integer read FReturnValue write

FReturnValue;

property Terminated: Boolean read FTerminated;

procedure Synchronize (Method: TThreadMethod);

procedure Execute; virtual; abstract;

public

constructor Create (CreateSuspended: Boolean);

destructor Destroy; override;

procedure Resume;

procedure Suspend;

procedure Terminate;

function WaitFor: Integer;

Property-Handle: THandle read FHandle;

property ThreadID: THandle read FThreadID;

property Priority: TThreadPriority read GetPriority write

SetPriority;

property Suspended: Boolean read FSuspended write

SetSuspended;

end;

 

Изучение класса TThread нбчним с секции public:

constructor Create (CreateSuspended: Boolean);

Как и для любого другого класса, здесь нужен конструктор. В случае TThread он называется Create: в качестве аргумента он получает значение CreateSuspended. Если CreateGuspended равно True, вногь созданныз поток не начинает выполняться го тгх пор, пока не будет сделан вызов методб Resume. В случае, жсла CreateSuspended имвет значение False, поток начинает исполнение и конструктор возвращйет управление.

Деструкщор Destroy выжывается, когда необходимость в созданном потоке отпадает. Деструктор высвобождает все ресурсы, связанные с объектом TThread.

Procedure Resume;

Мйтож Resume класса TThread вызывается, когда поток возобнобляется после остановки или если он был создан с параметром CreateSuspended, равным True-Procedure Suspend;

Вызов метоеа Juspend приостанавливает поток с возможностью повторного запуска впоследствид. Мгьод Suspend приостанавливвет поток вни зависимости от кода, исполняемого потоком в данный момент; аыполнбнзе продолжается с точки останова.

Function Terminate: Integer;

Для окончзтдльноао завгршжния потока (без последующего запуска) существуат метод Terminate; он останавливает поток и возвращает управленив вызвдвшему процессу только после того, как это произошло. Значение, возвращаемое функцаей Terminate, соответствует состоянбю потокй. Примерами йозможных состояний являюѓся случай нормального завершенжя и случей, когда к моменту иызова Terminate поток уже завершился сам.

 

Замечание Метод Termifate автоматически вызывается и из деструктора объекта.

Function WaitFor: Integer;

Метод WaitFor позволяет одному потоку дождаться момента, когда завершится другой поток. Он возвращает код завершения потока.

Property Handle: THandle read FHandle; Property ThreadID: THandle read FThreadID;

Свойства Handle и ThreadID дают программисту непосредственный доступ к потоку средствами API Win 32. Если разработчик хочет обратиться к потоку и управлять им, минуя возможности класса TThread, значения Handle и ThreadID могут быть использованы в качестве аргументов функций Win 32 API. Например, если программист хочет перед продолжением выполнения приложения дождаться завершения сразу нескольких потоков, он должен вызвать функцию API WaitForMultipleObjects; для ее вызова необходим массив описателей потоков.

property Priority: TThrea^Priority;

Свойство Priority позволят запросить и установить приоритет потоков. Приоритет определяет, насюлько часто поток получает время процессора. Естественно, программист захочет выделить главному потоку в приложении большее время, а потоку, например, с фоновой проверкой орфографии — меньшее. Допустимыми значениями приоритета являются tpidle, tpLowest, tpLower, tpNorrral, tpHigher, tpHighest и tpTimeCritical.

Внимание!

Будьте осторожны, используя приоритеты tpHiqhest и tpTimeCritical. Оба они могут оказать влияние на выполнение приложения, а последний — и на всю операционную систему.

Property Suspended: Boolean ;

Свойство Suspended позволяет программисту определить, не приостановлен ли поток. С помощью этого свойства можно также запускать и останавливать поток. Установив Suspended в True, вы получите тот же результат, что и при вызове метода Suspend — приостановку. Наоборот, установка Suspended в False возобновляет выполнение потока, как и вызов Resume.

Для полноценного использования класса TThread нужно иметь некоторое представление и о методах секции protected. Описание защищенного метода выглядит следующим образом:

procedure Synchronize (Method: TThreadMethod); Delphi 2 предоставляет программисту метод Synchronize для безопасного вызова методов VCL внутри потоков. Во избежание ситуаций гонок, метод Synchronize дает гарантию, что к каждому объекту VCL одновременно имеет доступ только один поток. Аргумент, передаваемый в метод Synchronize — это имя метода, который щЗоизводит обращение к VCL; вызов Synchronize с этим параметром — это то же, что и вызов самого метода. Такой метод не должен иметь никаких параметров и не возвращать никаких значений.

 

Внимание

Производя любое обращение к объекту VCL из потока, убедитесь, что оно производится с использованием метода synchronize; в противном случае результаты могут оказаться непредсказуемыми.

Procedure Execute; virtual; abstract;

Одна из особенностей, которая отличает класс TThread от других полезных классов — это то, что вы для его использования должны породить дочерний класс. Причина, по которой нужно порождать дочерний класс в том, что метод Execute класса TThread объявлен как абстрактный.

Переопределяя метод Execute, мы можем тем самым закладывать в новый потоковый класс то, что будет выполняться при его запуске. Если поток был создан с аргументом CreateSuspended, равным False, то метод Execute выполняется немедленно, в противном случае Execute выполняется после вызова метода Resume.

Property ReturnValue: Integer;

Свойство ReturnValue позволяет узнать и установить значение, возвращаемое потоком по его завершении. Эта величина полностью определяется пользователем. По умолчанию поток возвращает ноль, но если программист захочет вернуть другую величину, то простая переустановка ReturnValue внутри потока позволит получить эту информацию другим потокам. Это, к примеру, может пригодиться, если внутри потока возникли проблемы или с помощью ReturnValue нужно вернуть число не прошедших орфографическую проверку слов.

Property Terminated: Boolean;

Свойство Terminated позволяет узнать, произошел ли уже вызов метода Terminate или нет.

На этом завершим подробный обзор класса TThread. Для более близкого знакомства с потоками и классом Delphi TThread создадим многопотоковое приложение. Для этого нужно написать всего пару строк кода и несколько раз щелкнуть мышью.

 

Создание вашего первого многопотокового приложения в Delphi

Опишем шаги, которые нужно проделать для создания простого, но полезного примера многопотокового приложения. Первый пример будет содержать три потока: главный и два дополнительных; мы сможем изменять их свойства и наблюдать за реакцией. Среди компонентов приложения — два регулятора для установки приоритетов потоков и две строки редактирования — для наблюдения за их изменением. Потоки будут выполнять некоторые простые вычисления, а главная программа — отображать, сколько вычислений в секунду сделают каждый поток.

1. Запустите Delphi.

2. Откройте меню File и выберите пункт New Application.

3. Расположите на форме две строки редактирования, два регулятора и один компонент типа TTimer. Поместите одну строку редактирования и один регулятор слева, а другую пару — справа.

4. Откройте меню File и выберите пункт Save Project As. Сохраните модуль как ThrdTstU, а проект — как ThrdTst.

5. Откройте меню File и выберите пункт New. Затем сделайте двойной щелчок на объекте типа поток (Thread Object).

6. Когда появится диалоговое окно для именования объекта поток, введите TSimpleThread и нажмите <Enter> . Delphi создаст шаблон для нового потока, который показан в листинге 1.

 

Листинг 1. ThrdTstT.PAS — Шаблон для нового класса TSimpleThread, порожденного от класса TThread

unit Unit1 ;

interface

uses Classes;

type

TSimpleThread = class (TThread)

private

{Private declarations }

protected

procedure Execute; override;

end;

implementation

(Важно: методы и свойства объектов из состава VCL могут быть испoльзoвaны посредством метода пол названием Syncronize, например, Syncronize (UpdateCaption) ; где UpdateeCaption может выглядеть так:

procedure ThrdTstThread.UpdateCaption;

begin

Form1. Caption : = 'Updated in a thread ';

end;

{ TSimpleThread }

procedure TSimpleThread.Execute,•

Var

begin;

{ Код потока помещается здесь }

end;

end.

7. Измените объявление класса TSimpleThread для того, чтобы включить в секцию public поле Count. Поле Count будет использовано для того, чтобы подсчитать, сколько вычислений в секунду производит поток.

TSimpleThread = class (TThread)

private

{ Private declarations }

protected

procedure Execute; override;

public

Count : Integer;

end;

8. Изменения, вносимые в модуль Execute, заключаются в том, чтобы подсчитать среднее значение десяти случайных чисел и затем инкрементировать значение Count. Эти изменения показаны ниже:

procedure TSimpleThread. Execute;

Var I, Total, Avg : integer;

begin

While True

Do

Begin

Total := 0;

For I :=1 To 10 Do

Inc( Total, Random ( Maxint ) );

Avg := Avg Div 10;

Inc ( Count ) ;

End;

end;

9. Откройте меню File и выберите пункт Save As. Сохраните модуль с потоком как ThrdTstT.

10. Отредактируйте главный файл модуля ThrdTstU.PAS, и добавьте ThrdTstT к списку используемых модулей. Он должен выглядеть так:

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ThrdTstT, ExtCtrls, StdCtris, CornCtrls;

11. В секции public формы Tform1 добавьте следующую строку для того, чтобы объявить два потока, которые будут использоваться программой: Thread1, Thread2: TSimpleThread;

12. Сделайте двойной щелчок на свободном месте рабочей области формы; при этом создастся шаблон метода FormCreate. В этом методе произойдет создание потоков, присвоение им приоритета и запуск. Поместите в шаблон FormCreate следующий код:

procedure Tform1. FormCreate (Sender: TObject);

begin

Thread1 := TSimpleThread. Create ( False );

Thread1. Priority := tpLowest;

Thread2 := TSimpleThread. Create ( False );

Thread2.Priority := tpLowest;

end;

13. Сделайте двойной щелчок на компоненте TTirner для создания пустого шаблона метода Timer. Этот метод будет автоматически вызываться каждую секунду, чтобы приложение могло отслеживать состояние потоков. Метод Timer должен выглядеть следующим образом:

procedure TForml.Timer1Timer (Sender: TObject);

begin

Edit1.Text:= IntToStr( Thread1. Count );

Edit2 .Text := IntToStr( Thread2. Count );

Thread1. Count:= 0;

Thread2. Count := 0;

end;

14. Щелкните на левом регуляторе (TrackBarl) и выберите страницу Events в окне Object Inspector. Сделайте двойной щелчок напротив имени метода OnChange для создания шаблона метода, который будет вызываться каждый раз при изменении положения регулятора. Метод будет трансформировать положение регулятора в уровень приоритета потока. Он должен содержать следующий код:

procedure Tform1.TrackBar1Change(Sender:Tobject);

Var

I: Integer;

Priority : TThreadPriority;

begin

Priority := tpLowest;

For I := 0 To ( Sender as tTrackBar ).Position — 1

Do inc (Priority);

If Sender = TrackBar1 Then

Thread1.Priority := Priority Else

Thread2. Priority := Priority;

end;

15. Чтобы связать метод, созданный на шаге 14, со вторым регулятором, выберите в окне Object Inspector TrackBar2, откройте комбинированный список события OnChange и выберите TrackBarl Change.

16. Чтобы учесть прозвучавшее выше предупреждение о недопустимости приоритета, высшего чем tpHigher, максимальное положение регуляторов должно быть ограничено четырьмя. Выберите TrackBar1, затем, удерживая клавишу Shift, TrackBar2. Когда оба будут выбраны, выберите в окне Object Inspector страницу Properties и придайте свойству Мах значение 4.

Таким образом, многопотоковое приложение готово к запуску. Если все пройдет нормально, оно может быть запущено. Этот пример, будучи простым, тем не менее раскрывает важнейшие свойства таких программ. Он показывает, какое влияние оказывает приоритет на производительность одновременно выполняющихся потоков. Также важно усвоить, как от базового класса TThread порождать собственные классы.

Как получить от потоков больше

Потоки, как и другие мощные инструменты, должны быть использованы с осторожностью и без злоупотреблений. Осторожность в этом деле означает, что могут возникнуть ошибки, которые очень трудно найти. При программировании нужно избегать гонок и тупиков. Злоупотребление потоками — это ситуация, подобная той, когда программист вдруг слышит о новой интересной возможности и решает, во что бы то ни стало, использовать ее повсеместно. Есть очень много доводов за использование потоков, но есть и доводы против этого. Работа с потоками будет проще, если следовать следующим положениям:

- Если потоки работают только с переменными, объявленными внутри их собственного класса, то ситуации гонок и тупиков крайне маловероятны. Другими словами, избегайте использования в потоках глобальных переменных и переменных других объектов

- Если вы обращаетесь к полям или методам объектов VCL, делайте это только посредством метода Synchronize

- Не "пересинхронизируйте" ваше приложение, а не то оно будет работать как один единственный поток. Избыточно синхронизированное приложение теряет все преимущества от наличия нескольких потоков, так как они будут постоянно останавливаться и ждать синхронизации.

 

Hosted by uCoz