The Embarcadero DocWiki for Delphi recommends that “thread synchronization techniques should be based on SyncObjs.TEvent and SyncObjs.TMutex.”
There is, however, another synchronization class available since Delphi 2009: TMonitor. It uses the object lock which has been introduced in this version and which caused the size of TObject to increase by four bytes (for details read “Why Has the Size of TObject Doubled In Delphi 2009?“, which finds that “The ability to lock any object is a good feature to have, especially in a multi-CPU, multi-core world.”).
Using TMonitor.Wait
, the synchronized code waits until a lock is released without wasting processor time.
This example uses TMonitor.Wait
and TMonitor.PulseAll
, based on an article about guarded methods in the Java(tm) tutorials:
„This kind of application shares data between two threads: the producer, that creates the data, and the consumer, that does something with it. The two threads communicate using a shared object. Coordination is essential: the consumer thread must not attempt to retrieve the data before the producer thread has delivered it, and the producer thread must not attempt to deliver new data if the consumer hasn’t retrieved the old data.”
In this example, the data is a series of text messages, which are shared through an object of type Drop:
program TMonitorTest; {$APPTYPE CONSOLE} uses SysUtils, Classes; type Drop = class(TObject) private // Message sent from producer to consumer. Msg: string; // True if consumer should wait for producer to send message, false // if producer should wait for consumer to retrieve message. Empty: Boolean; public constructor Create; function Take: string; procedure Put(AMessage: string); end; Producer = class(TThread) private FDrop: Drop; public constructor Create(ADrop: Drop); procedure Execute; override; end; Consumer = class(TThread) private FDrop: Drop; public constructor Create(ADrop: Drop); procedure Execute; override; end; { Drop } constructor Drop.Create; begin Empty := True; end; function Drop.Take: string; begin TMonitor.Enter(Self); try // Wait until message is available. while Empty do begin TMonitor.Wait(Self, INFINITE); end; // Toggle status. Empty := True; // Notify producer that status has changed. TMonitor.PulseAll(Self); Result := Msg; finally TMonitor.Exit(Self); end; end; procedure Drop.Put(AMessage: string); begin TMonitor.Enter(Self); try // Wait until message has been retrieved. while not Empty do begin TMonitor.Wait(Self, INFINITE); end; // Toggle status. Empty := False; // Store message. Msg := AMessage; // Notify consumer that status has changed. TMonitor.PulseAll(Self); finally TMonitor.Exit(Self); end; end; { Producer } constructor Producer.Create(ADrop: Drop); begin FDrop := ADrop; inherited Create(False); end; procedure Producer.Execute; var Msgs: array of string; I: Integer; begin SetLength(Msgs, 4); Msgs[0] := 'Mares eat oats'; Msgs[1] := 'Does eat oats'; Msgs[2] := 'Little lambs eat ivy'; Msgs[3] := 'A kid will eat ivy too'; for I := 0 to Length(Msgs) - 1 do begin FDrop.Put(Msgs[I]); Sleep(Random(5000)); end; FDrop.Put('DONE'); end; { Consumer } constructor Consumer.Create(ADrop: Drop); begin FDrop := ADrop; inherited Create(False); end; procedure Consumer.Execute; var Msg: string; begin repeat Msg := FDrop.Take; WriteLn('Received: ' + Msg); Sleep(Random(5000)); until Msg = 'DONE'; end; var ADrop: Drop; begin Randomize; ADrop := Drop.Create; Producer.Create(ADrop); Consumer.Create(ADrop); ReadLn; end.
Discover more from Habarisoft Blog
Subscribe to get the latest posts sent to your email.