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

0

Поскольку каждый  поток  представляет собой  отдельный и независимый путь вы полнения программного кода внутри  процесса, было  бы логично предположить, что на определенном этапе потребуется какое либо средство хранения данных, связанных с каждым потоком. Существует три метода хранения данных, уникальных для каждого потока. Первый, и самый  простой, состоит в использовании локальных переменных (в стеке). Поскольку каждый  поток  получает собственный стек,  при  выполнении  од ной  процедуры или  функции он  будет иметь  и собственную копию  локальных пере менных. Второй метод  заключается в сохранении локальной информации в объекте, производном от класса  TThread. И,  наконец, можно  применить зарезервированное слово Object Pascal threadvar, чтобы  воспользоваться преимуществами хранения локальной информации потока на уровне операционной системы.

Хранение данных в объекте TThread

Если сравнивать два последних варианта хранения данных  потоков, то,  бесспор но, следует остановить выбор  на способе хранения данных в объекте, производном от класса  TThread, поскольку  он проще и эффективнее метода  с использованием заре зервированного слова  threadvar (речь о  нем  пойдет позже). Для  объявления  ло кальных данных  потока этим способом достаточно добавить их в определение класса, производного от TThread:

type

TMyThread = class(TThread)

private

FLocalInt: Integer;

FLocalStr: String;

.

.

.

end;CОВЕТ

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

Ключевое слово threadvar и API хранения данных потока

Как уже говорилось, каждому потоку для хранения локальных переменных предос тавляется собственный стек,  в то  время  как глобальные данные должны  совместно использоваться всеми  потоками внутри  приложения. Допустим, что  существует  про цедура,  которая устанавливает или  отображает значение глобальной переменной, причем она  построена так,  что  при  передаче ей текстовой строки происходит уста новка,  а при  передаче пустой  строки —  отображение этой  глобальной переменной. Такая процедура может иметь следующий вид:

var

GlobalStr: String;

procedure SetShowStr(const S: String);

begin

if S = ” then

MessageBox(0, PChar(GlobalStr), ‘The string is…’, MB_OK)

else

GlobalStr := S;

end;

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

Для  ситуаций,  подобных  описанной  выше,  в  Win32  предусмотрено  средство,  из

вестное под названием хранение локальных данных потоков (thread local storage), благо даря которому можно  создавать отдельные копии глобальных переменных для каждо го выполняющегося потока. Delphi  великолепно инкапсулирует это  средство с помо щью зарезервированного слова  threadvar. Необходимо лишь  объявить глобальные переменные, которые предполагается использовать раздельно для каждого  потока, внутри  раздела  threadvar (а не var) — и делу конец.  Переопределение переменной GlobalStr проще простого:

threadvar

GlobalStr: String;

Модуль,  код  которого  представлен в  листинге 5.3,  иллюстрирует именно  такую проблему.  Это  главный модуль  приложения Delphi, в форме которого содержится всего лишь одна кнопка.  По щелчку на этой  кнопке вызывается процедура установки, а затем  и отображения значения переменной GlobalStr. Затем  создается другой по ток, где устанавливается и отображается значение его собственной переменной GlobalStr. После  создания вторичного потока первичный вновь  вызывает процеду ру SetShowStr для отображения переменной GlobalStr.Пробуйте запустить это  приложение, объявив GlobalStr в разделе var, а не  в

threadvar. Результат выполнения будет существенно отличаться от предыдущего.

Листинг 5.3. Модуль 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), ‘The string is…’, 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(‘Hello world’);

SetShowStr(”);

TTLSThread.Create(‘Dilbert’);

Sleep(100);

SetShowStr(”);

end;

end.НА ЗАМЕТКУ

В этой демонстрационной программе после создания вторичного потока вызывается функция API Win32 Sleep(), которая объявляется следующим образом:

procedure Sleep(dwMilliseconds: DWORD); stdcall;

Функция Sleep() (объявленная как процедура) сообщает операционной системе о том, что текущий поток не нуждается в дополнительных циклах процессора в течение миллисекунд, заданных параметром dwMilliseconds. Эта процедура введена в дан- ный код для создания эффекта имитации условий, когда система работает в режиме большей многозадачности, а также из-за необходимости внесения в приложение “случайности” выполнения различных потоков.

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

В задачах, связанных с хронометражом, процедуру Sleep() необходимо использовать осторожно, поскольку она поможет справиться с отдельной проблемой на одной машине, но проблемы хронометража, которые не решены в общем виде, могут проявиться на дру-гом компьютере, особенно если эта машина работает значительно быстрее или медлен-

нее, чем первая, или обладает иным количеством процессоров.

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии