Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учебное пособие 300072.doc
Скачиваний:
2
Добавлен:
30.04.2022
Размер:
297.98 Кб
Скачать

Хранение локальных данных потоков

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

Существует три метода хранения данных, уникальных для каждого потока:

  1. Использование локальных переменных (в стеке). Поскольку каждый поток получает co6cтвенный стек, то при выполнении одной процедуры или функции он будет иметь и собственную копию локальных переменных.

  2. Сохранение локальной информации в объекте потомка класса TThread.

  3. Использование зарезервированного слова 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() следует осторожно использовать при работах связанных с решением загадочных проблем хронометража. Эта процедура может сносно справиться с частной проблемой на вашей машине, но проблемы хронометража, которые не решены в общем случае, могут проявиться на чужом компьютере, особенно если эта другая машина работает значительно быстрее или медленнее, чем ваша, или если число процессоров чужой машины не совпадает с вашим.