System.TMonitor

From Appmethod Libraries
Jump to: navigation, search

Object Pascal

  TMonitor = record
  strict private
    type
      PWaitingThread = ^TWaitingThread;
      TWaitingThread = record
        Next: PWaitingThread;
        Thread: TThreadID;
        WaitEvent: Pointer;
      end;
      { TSpinWait implements an exponential backoff algorithm for TSpinLock. The algorithm is as follows:
        If the CPUCount > 1, then the first 10 (YieldThreshold) spin cycles (calls to SpinCycle) will use a base 2
        exponentially increasing spin count starting at 4. After 10 cycles, then the behavior reverts to the same
        behavior as when CPUCount = 1.
        If the CPUCount = 1, then it will sleep 1ms every modulus 20 cycles and sleep 0ms every modulus 5 cycles.
        All other cycles simply yield (SwitchToThread - Windows, sched_yield - POSIX). }
      TSpinWait = record
      private const
        YieldThreshold = 10;
        Sleep1Threshold = 20;
        Sleep0Threshold = 5;
      private
        FCount: Integer;
      public
        procedure Reset; inline;
        procedure SpinCycle;
      end;
      { TSpinLock implements a very simple non-reentrant lock. This lock does not block the calling thread using a
        synchronization object. Instead it opts to burn a few extra CPU cycles using the above TSpinWait type. This
        is typically far faster than fully blocking since the length of time the lock is held is relatively few
        cycles and the thread switching overhead will usually far outpace the few cycles burned by simply spin
        waiting. }
      TSpinLock = record
      private
        FLock: Integer;
      public
        procedure Enter;
        procedure Exit;
      end;
    var
      [Volatile] FLockCount: Integer;
      FRecursionCount: Integer;
      FOwningThread: TThreadID;
      FLockEvent: Pointer;
      FSpinCount: Integer;
      FWaitQueue: PWaitingThread;
      FQueueLock: TSpinLock;
    class var CacheLineSize: Integer;
    class var FDefaultSpinCount: Integer;
    class procedure Spin(Iterations: Integer); static;
    class function GetCacheLineSize: Integer; static;
    procedure QueueWaiter(var WaitingThread: TWaitingThread);
    procedure RemoveWaiter(var WaitingThread: TWaitingThread);
    function DequeueWaiter: PWaitingThread;
    function GetEvent: Pointer;
    function CheckOwningThread: TThreadID;
    class procedure CheckMonitorSupport; static; inline;
  private
    class function Create: PMonitor; static;
    class procedure Destroy(const AObject: TObject); overload; static;
    procedure Destroy; overload;
  strict private
    class function GetFieldAddress(const AObject: TObject): PPMonitor; inline; static;
    class function GetMonitor(const AObject: TObject): PMonitor; static;
    class procedure SetDefaultSpinCount(AValue: Integer); static;
    function TryEnter: Boolean; overload;
    function Wait(ALock: PMonitor; Timeout: Cardinal): Boolean; overload;
    procedure Pulse; overload;
    procedure PulseAll; overload;
  private
    function Enter(Timeout: Cardinal): Boolean; overload;
    procedure Exit; overload;
  public
    { In multi-core/multi-processor systems, it is sometimes desirable to spin for a few cycles instead of blocking
      the current thread when attempting to Enter the monitor. Use SetSpinCount to set a reasonable number of times to
      spin before fully blocking the thread. This value usually obtained through empirical study of the particular
      situation.  }
    class procedure SetSpinCount(const AObject: TObject; ASpinCount: Integer); static;
    { Enter locks the monitor object with an optional timeout (in ms) value. Enter without a timeout will wait until
      the lock is obtained. If the procedure returns it can be assumed that the lock was acquired. Enter with a
      timeout will return a boolean status indicating whether or not the lock was obtained (True) or the attempt timed
      out prior to acquire the lock (False). Calling Enter with an INFINITE timeout is the same as calling Enter
      without a timeout.
      TryEnter will simply attempt to obtain the lock and return immediately whether or not the lock was acuired.
      Enter with a 0ms timeout is functionally equivalent to TryEnter.
      Exit will potentially release the lock acquired by a call to Enter or TryEnter. Since Enter/TryEnter are
      rentrant, you must balance each of those calls with a corresponding call to Exit. Only the last call to Exit will
      release the lock and allow other threads to obtain it. Runtime error, reMonitorNotLocked, is generated if Exit is
      called and the calling thread does not own the lock. }
    class procedure Enter(const AObject: TObject); overload; static; inline;
    class function Enter(const AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
    class procedure Exit(const AObject: TObject); overload; static;
    class function TryEnter(const AObject: TObject): Boolean; overload; static;
    { Wait will atomically fully release the lock (regardless of the recursion count) and block the calling thread
      until another thread calls Pulse or PulseAll. The first overloaded Wait function will assume the locked object
      and wait object are the same and thus the calling thread must own the lock. The second Wait allows the given
      monitor to atomically unlock the separate monitor lock object and block with the calling thread on the first
      given wait object. Wait will not return (even if it times out) until the monitor lock can be acquired again. It
      is possible for wait to return False (the timeout expired) after a much longer period of time has elapsed if
      the locking object was being held by another thread for an extended period. When Wait returns the recursion
      level of the lock has been restored.
      Pulse must be called on the exact same instance passed to Wait in order to properly release one waiting thread.
      PulseAll works the same as Pulse except that it will release all currently waiting threads.
      Wait/Pulse/PulseAll are the same as a traditional condition variable.
    }
    class function Wait(const AObject: TObject; Timeout: Cardinal): Boolean; overload; static;
    class function Wait(const AObject, ALock: TObject; Timeout: Cardinal): Boolean; overload; static;
    class procedure Pulse(const AObject: TObject); overload; static;
    class procedure PulseAll(const AObject: TObject); overload; static;
    class property DefaultSpinCount: Integer read FDefaultSpinCount write SetDefaultSpinCount;
  end;

C++

struct DECLSPEC_DRECORD TMonitor
{
private:
    struct TWaitingThread;
    typedef TWaitingThread *PWaitingThread;
    struct DECLSPEC_DRECORD TWaitingThread
    {
    public:
        TMonitor::TWaitingThread *Next;
        unsigned Thread;
        void *WaitEvent;
    };
    struct DECLSPEC_DRECORD TSpinWait
    {
    private:
        static const System::Int8 YieldThreshold = System::Int8(0xa);
        static const System::Int8 Sleep1Threshold = System::Int8(0x14);
        static const System::Int8 Sleep0Threshold = System::Int8(0x5);
        int FCount;
    public:
        void __fastcall Reset(void);
        void __fastcall SpinCycle(void);
    };
    struct DECLSPEC_DRECORD TSpinLock
    {
    private:
        int FLock;
    public:
        void __fastcall Enter(void);
        void __fastcall Exit(void);
    };
private:
    int FLockCount;
    int FRecursionCount;
    unsigned FOwningThread;
    void *FLockEvent;
    int FSpinCount;
    TWaitingThread *FWaitQueue;
    TSpinLock FQueueLock;
    static int CacheLineSize;
    static int FDefaultSpinCount;
    static void __fastcall Spin(int Iterations);
    static int __fastcall GetCacheLineSize();
    void __fastcall QueueWaiter(TWaitingThread &WaitingThread);
    void __fastcall RemoveWaiter(TWaitingThread &WaitingThread);
    PWaitingThread __fastcall DequeueWaiter(void);
    void * __fastcall GetEvent(void);
    unsigned __fastcall CheckOwningThread(void);
    static void __fastcall CheckMonitorSupport();
private:
    static PMonitor __fastcall Create();
    static void __fastcall Destroy(TObject* const AObject)/* overload */;
    void __fastcall Destroy(void)/* overload */;
private:
    static PPMonitor __fastcall GetFieldAddress(TObject* const AObject);
    static PMonitor __fastcall GetMonitor(TObject* const AObject);
    static void __fastcall SetDefaultSpinCount(int AValue);
    bool __fastcall TryEnter(void)/* overload */;
    bool __fastcall Wait(PMonitor ALock, unsigned Timeout)/* overload */;
    void __fastcall Pulse(void)/* overload */;
    void __fastcall PulseAll(void)/* overload */;
private:
    bool __fastcall Enter(unsigned Timeout)/* overload */;
    void __fastcall Exit(void)/* overload */;
public:
    static void __fastcall SetSpinCount(TObject* const AObject, int ASpinCount);
    static void __fastcall Enter(TObject* const AObject)/* overload */;
    static bool __fastcall Enter(TObject* const AObject, unsigned Timeout)/* overload */;
    static void __fastcall Exit(TObject* const AObject)/* overload */;
    static bool __fastcall TryEnter(TObject* const AObject)/* overload */;
    static bool __fastcall Wait(TObject* const AObject, unsigned Timeout)/* overload */;
    static bool __fastcall Wait(TObject* const AObject, TObject* const ALock, unsigned Timeout)/* overload */;
    static void __fastcall Pulse(TObject* const AObject)/* overload */;
    static void __fastcall PulseAll(TObject* const AObject)/* overload */;
    /* static */ __property int DefaultSpinCount = {read=FDefaultSpinCount, write=SetDefaultSpinCount};
};

Properties

Type Visibility Source Unit Parent
record
struct
public
System.pas
System.hpp
System System

Description

TMonitor provides methods for synchronizing the access of several threads to a single object.

Use the class methods of TMonitor to synchronize the threads' access to resources in a multithreaded application.

See Also