- •Гоувпо «Воронежский государственный технический университет»
- •Методические указания
- •Требования к выполнению и оформлению лабораторных работ
- •Практическое применение потоков
- •Основы работы объекта tThread
- •Завершение работы потока
- •Преимущества однопоточного интерфейса пользователя
- •Метод Synchronize
- •Хранение локальных данных потоков
- •Использование объекта tThread для хранения данных
- •Threadvar: хранение локальных данных потоков с помощью интерфейса api
- •Синхронизация потоков
- •Критические разделы
- •Мьютексы
- •Семафоры
- •Библиографический список
- •Содержание
- •394026 Воронеж, Московский просп., 14
Хранение локальных данных потоков
Поскольку каждый поток представляет отдельный путь выполнения приложения внутри процесса, было бы логично предположить, что на определенном этапе потребуется средство хранения данных, связанных с каждым потоком.
Существует три метода хранения данных, уникальных для каждого потока:
Использование локальных переменных (в стеке). Поскольку каждый поток получает co6cтвенный стек, то при выполнении одной процедуры или функции он будет иметь и собственную копию локальных переменных.
Сохранение локальной информации в объекте потомка класса TThread.
Использование зарезервированного слова Object Pascal threadvar, чтобы воспользоваться преимуществами хранения локальной информации потока на уровне операционной системы.
Использование объекта tThread для хранения данных
Если сравнивать последние два варианта хранения данных потоков, то следует остановить выбор на способе хранения данных в обьекте потомка класса TThread, поскольку он проще и эффективнее метода с использованием зарезервированного слова threadvar. А для объявления локальных данных потока более предпочтительным способом достаточно добавить их в определение потомка класса TThread:
type
TMyThread = class(TThread)
private
FLocalInt: Integer;
FLocalStr: String;
end;
Threadvar: хранение локальных данных потоков с помощью интерфейса api
Каждому потоку для хранения локальных переменных предоставляется его собственный стек, в то время как глобальные данные должны совместно использоваться всеми потоками внутри приложения. Например, у вас есть процедура, которая устанавливает или отображает значение глобальной переменной, причем она пocтроена так, что при передаче ей текстовой строки происходит установка, а при передаче пустой строки - отображение этой глобальной переменной. Такая процедура может иметь следующий вид:
var
GlobalStr: String;
procedure SetShowStr(const S: String);
begin
if S = '' then
MessageBox(0, PChar (GlobalStr), ‘Строка …’', MB_OK)
else
GlobalStr := S;
end;
Если эта процедура вызывается в контексте только одного потока, никаких проблем не возникнет. Один раз она будет вызвана для установки значения переменной GlobalStr, а второй - для отображения этого значения. Рассмотрим, что произойдет, если два или больше потоков смогут вызвать эту процедуру в произвольный момент времени. В этом случае возможна ситуация, когда один поток вызовет эту процедуру для установки строки, после чего процессорное время будет отдано другому потоку, который также вызовет эту процедуру для установки своей строки. И к тому времени, когда операционная система передаст обратно процессорное время первому потоку, значение переменной GlobalStr будет безнадежно утрачено.
Для ситуаций, подобных описанной выше, в Win32 предусмотрено средство, известное под названием хранение локальных данных потоков (thread-local storage), с помощью которого можно создавать отдельные копии глобальных переменных для каждого действующего потока. Delphi инкапсулирует это средство с помощью зарезервированного слова threadvar. Требуется лишь объявить любые глобальные переменные, которые вы собираетесь использовать отдельно для каждого потока, внутри блока threadvar (вместо var). Переопределение переменной GlobalStr:
Threadvar
GlobalStr: String;
Модуль, представленный в листинге 1, иллюстрирует именно эту проблему. В главном модуле приложения Delphi на форме содержится всего одна кнопка. При щелчке на этой кнопке вызывается процедура для установки, а затем и для отображения значения переменной GlobalStr. После этого создается другой поток, в котором устанавливается и отображается его собственное значение этой переменной. После создания вторичного потока первичный вновь вызывает процедуру SetShowStr для отображения переменной GlobalStr.
Попробуйте запустить это приложение, сначала объявив переменную GlobalStr с помощью зарезервированного слова var а затем - threadvar, и проанализируйте различие в результатах работы.
Листинг 1. Модуль MAIN.PAS — демонстрация хранения локальных переменных потоков.
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TMainForm = class (TForm)
Button1: TButton;
procedure Button1Click (Sender: TObject);
private
{ Закрытые объявления }
public
{ Открытые объявления }
end;
var
MainForm: TMainForm;
Implementation
{ $R *.DFM}
{ ЗАМЕЧАНИЕ : Перенесите переменную GlobalStr из блока var в блок threadvar и обратите внимание на разницу в результате работы приложения. }
//var
threadvar
GlobalStr: string;
type
TTLSThread = class (Tthread)
private
FNewStr: String;
protected
procedure Execute; override;
public
constructor Create (const ANewStr : String) ;
end;
procedure SetShowStr (const S: String) ;
begin
if S = " then
MessageBox(0, PChar (GlobalStr), 'Строка …', MB_OK)
else
GlobalStr := S;
end;
constructor TTLSThread.Create(const ANewStr: String);
begin
FNewStr := ANewStr;
inherited Create(False);
end;
procedure TTLSThread.Execute;
begin
FreeOnTerminate := True;
SetShowStr(FNewStr);
SetShowStr('');
end;
procedure TMainForm.Button1Click(Sender: TObject);
begin
SetShowStr ('Привет');
SetShowStr (' ') ;
TTLSThread.Create ('Ура');
Sleep(100);
SetShowStr('');
end;
end.
Примечание:
В этой демонстрационной программе после создания вторичного потока вызывается процедура Win32 API sleep(), которая объявляется следующим образом:
procedure Sleep(dwMilliseconds: DWORD); stdcall;
Процедура Sleep() сообщает операционной системе, что текущий поток не нуждается в дополнительных циклах процессора в течение миллисекунд, заданных параметром dwMilliseconds. Вставка вызова этой процедуры в данный код обусловлена необходимостью создания эффекта имитации условий, когда система работает в режиме большей многозадачности, а также необходимостью внесения в приложение случайностей, связанных с тем, когда и какой именно поток будет выполняться.
Часто приемлемым вариантом является передача в качестве параметра dwMilliseconds нулевого значения. И хотя в этом случае текущий поток не застрахован от работы в течение определенного времени, тем не менее такой ход заставляет операционную систему передать процессорные циклы одному из ожидающих потоков с равным или высшим приоритетом.
Процедуру sleep() следует осторожно использовать при работах связанных с решением загадочных проблем хронометража. Эта процедура может сносно справиться с частной проблемой на вашей машине, но проблемы хронометража, которые не решены в общем случае, могут проявиться на чужом компьютере, особенно если эта другая машина работает значительно быстрее или медленнее, чем ваша, или если число процессоров чужой машины не совпадает с вашим.