vložil Radek Červinka
2. června 2010 23:06
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.
1PPMonitor = ^PMonitor;
2 PMonitor = ^TMonitor;
3
4 TMonitor = record
5 strict private
6 type
7 PWaitingThread = ^TWaitingThread;
8 TWaitingThread = record
9 Next: PWaitingThread;
10 Thread: Cardinal;
11 WaitEvent: Pointer;
12 end;
13 var
14 FLockCount: Integer;
15 FRecursionCount: Integer;
16 FOwningThread: Cardinal;
17 FLockEvent: Pointer;
18 FSpinCount: Integer;
19 FWaitQueue: PWaitingThread;
20 procedure QueueWaiter(var WaitingThread: TWaitingThread);
21..
22 public
23 class procedure SetSpinCount(AObject: TObject; ASpinCount: Integer); static;
24 class procedure Enter(AObject: TObject); overload; static; inline;
25 class function Enter(AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
26 class procedure Exit(AObject: TObject); overload; static;
27 class function TryEnter(AObject: TObject): Boolean; overload; static;
28 class function Wait(AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
29 class procedure Pulse(AObject: TObject); overload; static;
30 class procedure PulseAll(AObject: TObject); overload; static;
31 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.
1function MonitorEnter(AObject: TObject; Timeout: Cardinal = INFINITE): Boolean; inline;
2function MonitorTryEnter(AObject: TObject): Boolean; inline;
3procedure MonitorExit(AObject: TObject); inline;
4function MonitorWait(AObject: TObject; Timeout: Cardinal): Boolean; inline;
5procedure MonitorPulse(AObject: TObject); inline;
6procedure MonitorPulseAll(AObject: TObject); inline;
7
8procedure MemoryBarrier;
9procedure 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
1System.TMonitor.Enter(list);
2 try
3 list.Add(….);
4 …
5 finally
6 System.TMonitor.Exit(list);
7 end;
ekvivalentem je volání přes uvedené procedury
1MonitorEnter(list);
2 try
3 list.Add(….);
4 …
5 finally
6 MonitorExit(list);
7 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.