V Delphi 2009 byla přidána pěkná možnost synchronizace přístupu k objektům. Do RTL, konkrétně system.pas, byl pro synchronizaci přidán záznam TMonitor (podobně je to řešeno v .NET).
Už několikrát jsem upozorňoval na opravdu nádherné možnosti získané díky rozšíření funkcionality record (alias záznamu). V starých Delphi byl záznam prostě jen blok strukturovaných dat, kdežto nyní nad ním můžete definovat metody, typy, class metody, případně class proměnné, viz. wiki Embarcadera. Níže schválně uvádím deklaraci TMonitor, samozřejmě nás zajímá jen sekce public, zbytek je jen jako příklad.
PPMonitor = ^PMonitor;
PMonitor = ^TMonitor;
TMonitor = record
strict private
type
PWaitingThread = ^TWaitingThread;
TWaitingThread = record
Next: PWaitingThread;
Thread: Cardinal;
WaitEvent: Pointer;
end;
var
FLockCount: Integer;
FRecursionCount: Integer;
FOwningThread: Cardinal;
FLockEvent: Pointer;
FSpinCount: Integer;
FWaitQueue: PWaitingThread;
procedure QueueWaiter(var WaitingThread: TWaitingThread);
..
public
class procedure SetSpinCount(AObject: TObject; ASpinCount: Integer); static;
class procedure Enter(AObject: TObject); overload; static; inline;
class function Enter(AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
class procedure Exit(AObject: TObject); overload; static;
class function TryEnter(AObject: TObject): Boolean; overload; static;
class function Wait(AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
class procedure Pulse(AObject: TObject); overload; static;
class procedure PulseAll(AObject: TObject); overload; static;
end;
Pojmenování TMonitor není moc štastné, jelikož v jednotce Forms.pas již jeden TMonitor existuje, takže pozor na záměnu.
Kromě samostatného záznamu je v RTL několik procedur, které lze jednoduše použít.
function MonitorEnter(AObject: TObject; Timeout: Cardinal = INFINITE): Boolean; inline;
function MonitorTryEnter(AObject: TObject): Boolean; inline;
procedure MonitorExit(AObject: TObject); inline;
function MonitorWait(AObject: TObject; Timeout: Cardinal): Boolean; inline;
procedure MonitorPulse(AObject: TObject); inline;
procedure MonitorPulseAll(AObject: TObject); inline;
procedure MemoryBarrier;
procedure YieldProcessor;
Jen pro úplnost: veškeré synchronizace jsou nahraditelné za vlastní obsluhu. Metody z TMonitor interně volají procedury definované přes System.MonitorSupport, takže pokud se vám nelíbí standardní obsluha (definovaná v SysUtils), nic Vám nebrání do System.MonitorSupport přiřadit vlastní implementace.
Použití TMonitor je přímočaré: kdekoliv potřebujeme přistupovat ke sdíleným datům z více vláken napíšeme např. pro přístup ke sdílenému TList
System.TMonitor.Enter(list);
try
list.Add(….);
…
finally
System.TMonitor.Exit(list); //vždy přes finally aby nedošlo při chybě k zablokování v tomto případě list
end;
ekvivalentem je volání přes uvedené procedury
MonitorEnter(list);
try
list.Add(….);
…
finally
MonitorExit(list); //vždy přes finally, aby nedošlo při chybě k zablokování v tomto případě na objektu list
end;
Kromě Enter a Exit, lze ještě využít Enter s dobou čekání (tj. vlákno bude čekat jen určitý čas na přístup k objektu jinak se volání ukončí a vrátí se False.
Jinak ještě je možnost TryEnter, kdy se jen pokusíme o přístup bez čekání, Wait kdy se daný objekt zamkne po určitou dobu nebo Pulse a PulseAll co podle nápovědy jejich volání upozorní čekající vlákno(a), že první (jeden z čekajících) může zamknout objekt hned po opuštění drženým vláknem. Přiznám se, že mne moc nenapadá na co by to mohlo být dobré (třeba to někdo v komentářích ujasní). Ale tipuji, že většinou bude stačit kombinace Enter - Exit jak bylo naznačeno v příkladu.