Понимание потоковых моделей в COM при программировании на Delphi - Объекты STA и защита данных

ОГЛАВЛЕНИЕ

 

Правило #6:

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

Для иллюстрации сказанного рассмотрим следующее: 

var
  iGlobal : integer;
type
  TObject1 = class (TAutoObject)
protected
  FLocal : integer;
  procedure AccessGlobal;
  procedure AccessLocal;
end;

procedure TObject1.AccessGlobal;
begin
  // Если создается несколько экземпляров TObject1 в раздельных STA и
  // AccessGlobal вызывается в каждом из них одновременно, то возможно,
  // что iGlobal будет испорчена
  // делаем что-либо с глобальной переменной
  iGlobal := iGlobal + 1;
end;

procedure TObject1.AccessLocal;
begin
  // Если создается один экземпляр TObject1 в STA и множество потоков
  // пытаются вызвать AccessLocal в этом единственном экземпляре,
  // то локальная переменная гарантировано не будет испорчена, так как
  // COM обеспечит, что вызовы AccessLocal следуют последовательно
  // друг за другом в пределах одного потока, живущего в этом STA
  // делаем что-либо с локальной переменной
  FLocal := FLocal + 1;
end;

Следовательно, правильным способом исправить TObject1.AccessGlobal является сделать переменную iGlobal защищенной от одновременного доступа из нескольких потоков STA. Простейший способ сделать это - это использовать критические секции Win32, которые обслуживаются операционной системой для таких случаев (я буду полагать, что Вы уже знаете некоторые основы потоков и функций работы с ними, существующие в Win32. Вы должны обратиться к книгам, если Вы чувствуете пробел в своих познаниях в этом месте. Вот книга, которую я нашел достаточно полезной "Win32 Multithreaded Programming"; Cohen и Woodring, O`Reilly, ISBN 1-56592-296-4). 

Тогда TObject1.AccessGlobal может быть переписана следующим образом:

uses
  SyncObjs;
var
  csGlobal : TCriticalSection;
  iGlobal : integer;
procedure TObject1.AccessGlobal;
begin
  // вход и выход из критической секции теперь гарантирует, что доступ
  // к iGlobal может быть осуществлен одним потоком единовременно и
  // невозможно разрушение при доступе из различных потоков
  csGlobal.Enter;
  try
    // делаем что-либо с глобальной переменной
    iGlobal := iGlobal + 1;
  finally
    csGlobal.Leave;
  end;  { finally }
end;

initialization
  csGLobal := TCriticalSection.Create;
finalization
  csGlobal.Free;
end.

Стороннее замечание. Delphi предоставляет в Ваше распоряжение класс TCriticalSection, являющийся оболочкой для примитивов критической секции Win32. TCriticalSection располагается в модуле SyncObjs, который, как я знаю, существует только в версии клиент/сервер Delphi. Если Вы не располагаете приобретенной копией клиент-серверной версии Delphi (как я), не все потеряно: Вы можете использовать класс TCriticalSection из библиотеки ThreadComLib этой статьи или, если Вы чувствуете в этом силу, использовать вызовы Win32 API. 

Подведем небольшой итог. Я как всегда отмечаю, что к объекту COM, живущему в STA можно получить доступ из множества потоков, живущих в других STA и что в случае одновременного доступа к нему все потоки выстраиваются в очередь вне зависимости от того, откуда этот поток пришел. Я Вам еще не сказал, что это гарантируется самим COM, но для того, чтобы COM имел возможность правильно исполнить эту возможность, Вы со своей стороны должны проделать некоторую работу. Для ясности в будущем, давайте предположим, что Вы имеете два потока STA в Вашем клиентском приложении и Вы создаете объект COM в первом потоке. Если Вы хотите, чтобы второй поток имел возможность доступа к этому же объекту (созданному в первом потоке), то Вы не можете просто получить указатель на интерфейс к объекту (созданному из первого потока) и начать вызывать его методы. Что Вы должны делать или, что более важно, COM позволяет делать Вам - это получить указатель на интерфейс и каким-то образом транслировать его из потока 1 в поток 2 так, чтобы когда поток 2 попытается получить доступ к этому интерфейсу COM знал бы, что этот интерфейс действительно используется вторым (отличным от первого) потоком и, таким образом, чтобы он знал о необходимости установления очередности с первым STA. Чтобы не забыть, сформулируем это в виде правила: