Модели потоков. Потокозащищенные классы Delphi

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

   
Многие классы Delphi имеют методы, которые позволяют синхронизировать доступ к переменным классов при обращении к ним из различных потоков.

   
Класс TCanvas - имеет методы Lock и Unlock. После вызова метода Lock рисование на полотне (canvas) возможно только из потока, откуда осуществлен вызов этой команды, а
остальные потоки находятся в состоянии ожидания. Пример, который обсуждался на 285 шаге - вывод графики на экран при одновременной сортировке случайного массива в разных потоках, - можно
переписать с использованием этих методов:

procedure TSortThread.DoVisualSwap;
begin	
  with FBox do	
  try
    Canvas.Lock;	
    Canvas.Pen.Color	:= clBtnFace;
    PaintLine(Canvas, FI, FA);
    PaintLine(Canvas, FJ, FB);
    Canvas.Pen.Color := clRed;
    PaintLine(Canvas, FI, FB);
    PaintLine(Canvas, FJ, FA);
  finally	
    Canvas.Unlock;	
  end;	
end;	

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

   
Графические объекты - потомки TGraphicsObject, которые инкапсулируют функциональность графических объектов Windows - кисть (TBrush), перо (ТРеn) и шрифт (TFont), также имеют
методы Lock и Unlock. Использование и назначение их такое же, как и в случае класса TCanvas.

   
Методами Lock и Unlock, которые используются для защиты переменных при доступе к ним из разных потоков, также обладают следующие классы Delphi:

  • TInterfaceList - хранит список интерфейсов;
  • TRemoteDataModule - используется в серверной части при создании DataSnap-приложений. Методы Lock и Unlock вызываются автоматически при работе сервера в модели разделенных
    потоков. Если используется модель свободных потоков, то программисту следует в явном виде вызывать эти методы.

   
Класс TThreadList наиболее часто используется при создании многопоточных приложений. Он хранит массив указателей, причем берет у операционной системы столько ресурсов, сколько надо для хранения массива
данной размерности. Путем приведения типов в нем можно хранить и целочисленные переменные (integer), и переменные с плавающей точкой (single). Перекрыв деструктор класса, в классе-потомке можно
хранить список объектов. Эти разнообразные возможности и делают данный класс часто применимым. Типичный пример работы с классом TThreadList:

var
  UserList:TThreadList = nil;

procedure TTest.AfterConstruction;
var
  L:TList;
begin
  inherited;
  try
    L := UserList.LockList;
    L.Add(Self);
  finally
    UserList.UnlockList;
  end;
end;

initialization
  TComponentFactory.Create(ComServer, TTest,
    Class_Test, ciMultiInstance, tmFree);
  UserList := TThreadList.Create;
finalization
  UserList.Free;
end.

   
В глобальной переменной UserList сохраняются ссылки на клиентов, которые обращаются к серверу за данными. Вызов метода LockList возвращает ссылку на класс TList, с которым манипулируют как с
обычным списком. После вызова метода UnlockList использовать полученную ссылку на список запрещается.
Каждому вызову метода Lock должен соответствовать вызов метода Unlock. Поэтому во всех описанных выше классах методы блокировки доступа (Lock, LockList) и снятия блокировки
(Unlock, UnlockList) следует помещать в защищенный блок try...finally...end. Несоблюдение этого правила приведет к тому, что к данному экземпляру класса нельзя будет обратиться из других потоков, кроме
потока, вызвавшего метод Lock. Поскольку все описанные выше блокировки реализованы через критические секции, то поток, который вызвал метод Lock, может повторно его вызвать и выполнить код после
этого вызова, даже если метод Unlock после первого вызова выполнен не был. Это затрудняет поиск ошибок, которые возникают при невыполненном методе Unlock.

   
Выводы.

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

  • Если приложения или потоки одного процесса изменяют общий ресурс, защищайте доступ к нему при помощи критических секций или мьютексов.
  • Если доступ осуществляется только на чтение, защищать ресурс не обязательно.
  • Хотя критические секции более эффективны, они применимы только внутри одного процесса, поэтому для синхронизации между процессами следует применять мьютексы.
  • Используйте семафоры для ограничения количества обращений к одному ресурсу.
  • Используйте объекты событий для информирования потока о наступлении какого-либо события.
  • Если разделяемый ресурс - 32-битная переменная, то для синхронизации доступа к нему можно использовать функции, обеспечивающие совместный доступ к переменным.
  • Многие объекты Win32 позволяют организовать эффективное слежение за своим состоянием при помощи функций ожидания. Используйте функции ожидания, поскольку они предлагают наиболее эффективный с точки зрения расхода системных ресурсов метод.
  • Если ваш поток создает (даже неявно, при помощи функции CoInitialize или функций DDE) окна, он должен обрабатывать сообщения. Не используйте в таком потоке функций, не позволяющих прервать ожидание по
    приходу сообщения с большим или неограниченным периодом ожидания, используйте функции MsgWaitForXXX.

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

   
Со следующего шага мы начнем рассматривать создание внутрипроцессорных серверов автоматизации.



Вы можете оставить комментарий, или Трекбэк с вашего сайта.

Оставить комментарий