Delphi - Есть ли эквивалент блокировки С#?

Я пишу многопоточное приложение в Delphi и должен использовать что-то для защиты общих ресурсов.

В С# я бы использовал ключевое слово "lock":

private someMethod() {
    lock(mySharedObj) {
        //...do something with mySharedObj
    }
}

В Delphi я не мог найти ничего подобного, я нашел метод TThread.Synchronize(someMethod), который предотвращает возможные конфликты, вызывая someMethod в основном потоке VCL, но это не совсем то, что я хочу сделать....

Изменить: я использую Delphi 6

Ответы

Ответ 1

(Un), к счастью, вы не можете блокировать произвольные объекты в Delphi 6 (хотя вы можете в более поздних версиях, 2009 и последующих), поэтому вам нужно иметь отдельный объект блокировки, обычно критический раздел.

TCriticalSection (примечание: документация из FreePascal, но она существует и в Delphi):

Пример кода:

type
  TSomeClass = class
  private
    FLock : TCriticalSection;
  public
    constructor Create();
    destructor Destroy; override;

    procedure SomeMethod;
  end;

constructor TSomeClass.Create;
begin
  FLock := TCriticalSection.Create;
end;

destructor TSomeClass.Destroy;
begin
  FreeAndNil(FLock);
end;

procedure TSomeClass.SomeMethod;
begin
  FLock.Acquire;
  try
    //...do something with mySharedObj
  finally
    FLock.Release;
  end;
end;

Ответ 2

В Delphi нет эквивалента 6. С Delphi 2009 вы можете использовать методы System.TMonitor для захвата блокировок на произвольных объектах.

System.TMonitor.Enter(obj);
try
  // ...
finally
  System.TMonitor.Exit(obj);
end;

(Вам нужен префикс "Система", потому что имя TMonitor конфликтует с типом в модуле Forms. Альтернативой является использование глобального MonitorEnter и MonitorExit.)

Ответ 3

Хотя это не так просто, как С#, следующие могут работать для вас.

  with Lock(mySharedObj) do
  begin
    //...do something with mySharedObj
    UnLock;
  end;

В двух словах

  • список хранится для каждого экземпляра, который вы хотите защитить.
  • когда второй поток имеет значение Lock(mySharedObj), внутренний список будет искать существующий замок. Новая блокировка будет создана, если не будет найдена существующая блокировка. Новый поток будет заблокирован, если другой поток по-прежнему заблокирован.
  • необходим Unlock, потому что мы не можем быть уверены, что ссылка на экземпляр ILock только выйдет из области в конце метода, вызывающего Lock. (Если бы мы могли, Unlock можно было бы удалить).

Обратите внимание, что в этом дизайне один TLock создается для каждого экземпляра объекта, который вы хотите защитить, без его освобождения до завершения приложения.
Это может быть учтено, но это будет связано с беспорядками с _AddRef и _Release.


unit uLock;

interface

type
  ILock = interface
    ['{55C05EA7-D22E-49CF-A337-9F989006D630}']
    procedure UnLock;
  end;

function Lock(const ASharedObj: TObject): ILock;

implementation

uses
  syncobjs, classes;

type
  _ILock = interface
    ['{BAC7CDD2-0660-4375-B673-ECFA2BA0B888}']
    function SharedObj: TObject;
    procedure Lock;
  end;

  TLock = class(TInterfacedObject, ILock, _ILock)
  private
    FCriticalSection: TCriticalSection;
    FSharedObj: TObject;
    function SharedObj: TObject;
  public
    constructor Create(const ASharedObj: TObject);
    destructor Destroy; override;
    procedure Lock;
    procedure UnLock;
  end;

var
  Locks: IInterfaceList;
  InternalLock: TCriticalSection;

function Lock(const ASharedObj: TObject): ILock;
var
  I: Integer;
begin
  InternalLock.Acquire;
  try
    //***** Does a lock exists for given Shared object
    for I := 0 to Pred(Locks.Count) do
      if (Locks[I] as _ILock).SharedObj = ASharedObj then
      begin
        Result := ILock(Locks[I]);
        Break;
      end;

    //***** Create and add a new lock for the shared object
    if not Assigned(Result) then
    begin
      Result := TLock.Create(ASharedObj);
      Locks.Add(Result);
    end;
  finally
    InternalLock.Release;
  end;
  (Result as _ILock).Lock;
end;

{ TLock }

constructor TLock.Create(const ASharedObj: TObject);
begin
  inherited Create;
  FSharedObj := ASharedObj;
  FCriticalSection := TCriticalSection.Create;
end;

destructor TLock.Destroy;
begin
  FCriticalSection.Free;
  inherited Destroy;
end;

procedure TLock.Lock;
begin
  FCriticalSection.Acquire;
end;

function TLock.SharedObj: TObject;
begin
  Result := FSharedObj;
end;

procedure TLock.UnLock;
begin
  FCriticalSection.Release;
end;

initialization
  Locks := TInterfaceList.Create;
  InternalLock := TCriticalSection.Create;

finalization
  InternalLock.Free;
  Locks := nil

end.

Ответ 4

Как сказано, для короткого кода, который не вызывается за пределами локальной области и не получает никаких других блокировок, вы можете использовать критические разделы через SyncObjs.TCriticalSection,
для более длинного/более сложного кода вы можете использовать SyncObjs.TMutex, который является необходимым (с таймаутом), не останавливается, если собственный поток умирает и может использоваться совместно с другими процессами.
Использование этих оболочек облегчает внесение изменений в уровень синхронизации.

Во всех случаях остерегайтесь драконов здесь: мой ответ на Разницу между функцией WaitFor для TMutex delphi и эквивалентом в win32 API

Ответ 5

Используя помощники классов, вы можете использовать это. Однако не будет работать со старыми версиями. Но я бы посоветовал использовать TMonitor только в XE5. Поскольку его довольно много медленнее, чем TRTLCriticalSection.

http://www.delphitools.info/2013/06/06/tmonitor-vs-trtlcriticalsection/

THelper = class helper for TObject
  procedure Lock;
  procedure Unlock;
end;

procedure THelper.Lock;
begin
  System.TMonitor.Enter(TObject(Self));
end;

procedure THelper.Unlock;
begin
  System.TMonitor.Exit(TObject(Self));
end;