Writing a simple threaded queue class using TMonitor (1)

While it’s been in the product for a few versions now, I’ve come across very little example code for TMonitor, despite its implementation having been heavily blogged about by its author (Allen Bauer) while he actually was writing it. In this post and its sequel, I’ll be looking to help rectify that, if only in a very small way.

Naturally, your first question may well be to ask what on earth is TMonitor. One answer is that it is a Delphi implementation of the Monitor class in .NET, having a very similar interface to the latter and essentially the same functionality. Of course, .NET didn’t invent the concept of a ‘monitor’, and you can read more about it on Wikipedia. In short, a monitor is the combination of a critical section and a ‘condition variable’. If you’re like me, the second part of this sounds very jargon-y, with the first leading to the thought ‘what’s the point?’ – after all, Delphi has a TCriticalSection class, or if you prefer, direct access to the Windows critical section API. In contrast, in .NET, Monitor is the critical section class, which – naturally enough – makes it quite essential over there (the fact the critical section aspect is also given some syntactical sugar in C# with the lock keyword can’t not help either). So, what is the point back in Delphi-land?

In essence, what TMonitor adds over a regular critical section object is a signalling capability — a very primitive signalling capability admitedly, but a signalling capability nonetheless. To give a flavour, here’s a more or less direct Delphi translation of a C# example of Joe Albahari’s (source):

program SimpleWaitPulse;

{$APPTYPE CONSOLE}

uses
  Classes;

var
  Go: Boolean;
  Thread: TThread;
begin
  Thread := TThread.CreateAnonymousThread(
    procedure
    begin
      TMonitor.Enter(Thread);
      try
        while not Go do
          TMonitor.Wait(Thread, INFINITE);
        WriteLn('Woken!!!');
      finally
        TMonitor.Exit(Thread);
      end;
    end);
  Thread.Start;
  ReadLn;
  TMonitor.Enter(Thread);
  try
    Go := True;
    TMonitor.Pulse(Thread);
  finally
    TMonitor.Exit(Thread);
  end;
  ReadLn;
end.

This starts a new thread that, on taking the lock, immediately releases it to wait on the Go variable becoming True. The latter happens when the user presses ENTER, upon which the main thread takes the lock, sets Go to True, ‘pulses’ the change in state, before finally releasing the lock.

Even though this is a very simple example – so simple in fact it fails to show up WriteLn’s lack of thread safety (ahem)! – there’s a few things to note from it:

  • As always with locking primitives, use of try/finally is very much necessary.
  • The sort of state waited on is entirely up to the discretion of the code that uses TMonitor.
  • The Wait and Pulse calls are made within an Enter/Exit pair. The call to Wait therefore releases the lock, allowing another thread to get in and call Pulse. The Pulse call itself then wakes the thread at the top of the Wait queue. In the present example there’s only one waiter, but if there were more than one and you wished to wake all of them, you should call PulseAll rather than Pulse several times.
  • Regarding the Wait call again, observe how it is made in the context of a while loop. In this case that isn’t strictly necessary, since there’s only going to be one waiter waiting on a single boolean condition. Matters would be different, though, whenever there is more than one consumer — between being released and acting on the pulsed state, that state may have been changed by another newly-released thread.

With this knowledge, we can use TMonitor to implement a simple threaded queue class, wrapping the standard TQueue. As for the actual implementation, you’ll have to wait to my next post though.

8 thoughts on “Writing a simple threaded queue class using TMonitor (1)

  1. HI, I have tested you program, It may seems has a problem.

    When the program executed to the line TMonitor.Exit(Locker) in the anonymous thread,
    Locker may has been freed at the line Locker.Free;

    In order to run always normally, I think place the last ReadLn statement before finally may fine.

    • Yep, you’re right. My original translation of the C# code locked against the thread object itself, which avoided that problem, but I decided to change it at the last minute so as not to promote the inherent bad practice of locking against a global object. Changed it back now.

  2. Without go variable, this program can has the same behavior. Following is the modified program:

    program SimpleWaitPulse;

    {$APPTYPE CONSOLE}

    uses
    Classes;

    var
    Locker: TObject;
    begin
    Locker := TObject.Create;
    try
    TThread.CreateAnonymousThread(
    procedure
    begin
    TMonitor.Enter(Locker);
    try
    TMonitor.Wait(Locker, INFINITE);
    WriteLn(‘Woken!!!’);
    finally
    TMonitor.Exit(Locker);
    end;
    end).Start;
    Readln;
    TMonitor.Enter(Locker);
    try
    TMonitor.Pulse(Locker);
    finally
    TMonitor.Exit(Locker);
    end;
    ReadLn;
    finally
    Locker.Free;
    end;

    end.

  3. I am still not quite sure I understand the point of TMonitor. I guess I’ll just wait for the next post, maybe I’ll get it then.

  4. Wots wrong with a TObjectQueue descendant, a CS and a semaphore? Like Thomas, I’m not quite sure about TMonitor – don’t see where the advantage is over ‘normal’ OS synchro mechanisms.

    • A few points:
      1. A monitor is a ‘normal’ synchronisation primitive.
      2. Nothing is wrong with semaphores and critical sections. Indeed, nothing is wrong with event objects either!
      3. See the post following this one for a (generic) TQueue wrapper (link).
      4. The code you’ve posted in the forums is OK, but IMO suffers from the following issues:
      – You should avoid the non-generic TQueue because it’s crap – it always pushes items onto the front on the internal TList (the generic version in contrast does things properly, maintaining an internal index). Also, it’s better design to encapsulate rather than inherit here – the way you’ve done it allows assigning to a regular TQueue object, and therefore, allows external code to call the unsafe ‘hidden’ members.
      – Your code suffers from a potential race condition in TsemaphoreMailbox.pop. The TMonitor-based code in my ‘part 2’ post avoids this.
      – Using pointers to reference types is pretty yucky IMO, sorry…
      – The TMonitor version (which does pretty much exactly the same as yours) is 100% in the XE RTL, which may be a bonus should cross platform Delphi ever become a solid reality.That said, you could probably recode your version to be the same easily enough.
      Neither your code nor mine attempts to do what TThreadedQueue does, only properly, since TThreadedQueue implements a capacity.

Leave a comment