5. Miscellaneous

This chapter covers some OmniThreadLibrary classes, records, and interfaces, that are useful for everyday programming but somehow did not find place in any other chapter.

5.1 TOmniTwoWayChannel

The OtlComm unit implements a TOmniTwoWayChannel class with a corresponding IOmniTwoWayChannel interface, which defines a bidirectional communication channel. This channel consists of two unidirectional channels, which are exposed through two IOmniCommunicationEndpoint interfaces.

 1 type
 2   IOmniTwoWayChannel = interface ['{3ED1AB88-4209-4E01-AA79-A577AD719520}']
 3     function Endpoint1: IOmniCommunicationEndpoint;
 4     function Endpoint2: IOmniCommunicationEndpoint;
 5   end;
 6   
 7   TOmniTwoWayChannel = class(TInterfacedObject, IOmniTwoWayChannel)
 8     constructor Create(messageQueueSize: integer; taskTerminatedEvent: THandle);
 9     destructor  Destroy; override;
10     function Endpoint1: IOmniCommunicationEndpoint; inline;
11     function Endpoint2: IOmniCommunicationEndpoint; inline;
12   end; 
13 
14   function CreateTwoWayChannel(numElements: integer = CDefaultQueueSize;
15     taskTerminatedEvent: THandle = 0): IOmniTwoWayChannel;

This interface is used internally for task controller/task communication, but can be also used in your own code.

One side on communication should use Endpoint1 endpoint and its Send/ Receive methods while the other side should use the Endpoint2 endpoint and its Send/Receive methods. Whatever is sent to Endpoint1 can be received on Endpoint2 and whatever is sent to Endpoin2 can be received on Endpoint1.

See also: RegisterComm/UnregisterComm in sections RegisterComm and IOmniTask Interface. Another example can be found in the 8_RegisterComm demo.

5.2 TOmniValueContainer

The TOmniValueContainer class implements a growable list of TOmniValue values, indexed with an integer and string values. It is used internally in the TOmniValue.AsArray and for task parameter passing.

 1 type
 2   TOmniValueContainer = class
 3   public
 4     constructor Create;
 5     procedure Add(const paramValue: TOmniValue; paramName: string = '');
 6     procedure Assign(const parameters: array of TOmniValue);
 7     procedure AssignNamed(const parameters: array of TOmniValue);
 8     function  ByName(const paramName: string): TOmniValue; overload;
 9     function  ByName(const paramName: string; 
10       const defValue: TOmniValue): TOmniValue; overload;
11     function  Count: integer;
12     function  Exists(const paramName: string): boolean;
13     function  IndexOf(const paramName: string): integer;
14     procedure Insert(paramIdx: integer; const value: TOmniValue);
15     function  IsLocked: boolean; inline;
16     procedure Lock; inline;
17     property Item[paramIdx: integer]: TOmniValue read GetItem write SetItem; default;
18     property Item[const paramName: string]: TOmniValue read GetItem write SetItem; default;
19     property Item[const param: TOmniValue]: TOmniValue read GetItem write SetItem; default;
20   end;

Add adds a new value to the list. Index can be an integer value (starting with 0) or a string value. This method will raise an exception if the container is locked (see below).

Assign assigns an array of TOmniValues to the container. Previous values stored in the container are lost (see the example for TOmniValue.CreateNamed for more information). This method will raise an exception if the container is locked (see below).

AssignNamed assigns an array of named values to the container. Elements of the array must alternate between names (string indexes) and values. Previous values stored in the container are lost. This method will raise an exception if the container is locked (see below).

ByName searches for a value with the specified name and returns the value. Searching is linear. Because of that, TOmniValueContainer should not be used to store large quantity of string-indexed data. One version of the function returns TOmniValue.Null if the string key is not found, while the other returns the default value, passed to the function call.

Count returns the current size of the container.

Exists checks whether a string-indexed value with the specified name is stored in the container.

IndexOf returns an integer index associated with the string-indexed value. This index can be used to access the value in the container.

Insert inserts new value into an integer-indexed array. This method will raise an exception if the container is locked (see below).

IsLocked checks where the container is locked. Locked container will not accept new values.

Lock locks the container. That prevents the code from changing the container. From that point, values can only be read from the container, not written to. A container cannot be unlocked.

Item property accesses a specific value either by an integer index (from 0 to Count-1), or by string index. If a TOmniValue is passed as an index, the type of the TOmniValue index parameter will determine how the container is accessed.

5.3 TOmniCounter

The CreateCounter (OtlCommon unit) function creates a counter with an atomic increment and decrement operations. Such counter can be used from multiple threads at the same time without any locking. Accessing the counter’s value is also thread-safe.

The counter is returned as an IOmniCounter interface. It is implemented by the TOmniCounter class, which you can use in your code directly if you’d rather deal with objects than interfaces.

 1 type
 2   IOmniCounter = interface ['{3A73CCF3-EDC5-484F-8459-532B8C715E3C}']
 3     function  Increment: integer;
 4     function  Decrement: integer;
 5     function  Take(count: integer): integer; overload;
 6     function  Take(count: integer; var taken: integer): boolean; overload;
 7     property Value: integer read GetValue write SetValue;
 8   end; 
 9 
10   TOmniCounter = record
11     procedure Initialize;
12     function  Increment: integer;
13     function  Decrement: integer;
14     function  Take(count: integer): integer; overload;
15     function  Take(count: integer; var taken: integer): boolean; overload;
16     property Value: integer read GetValue write SetValue;
17   end;
18   
19   function  CreateCounter(initialValue: integer = 0): IOmniCounter;

The counter part of the TOmniCounter record is automatically initialized on the first use. If you want, you can call ‘Initialize’ in advance, although that is not required.

Take is a special operation which tries to decrement the counter by count but stops at 0. It returns the number that could be taken from the counter (basically, Min(count, counter.Value)). Its effect is the same as the following code (except that the real implementation of Take is thread-safe).

1 Result := Min(counter, count);
2 counter := counter - Result;

Take is used in demo Parallel Data Production.

5.4 TOmniAlignedInt32 and TOmniAlignedInt64

Those two records hold 4-byte (32 bit) and 8-byte (64 bit) values, respectively. THese values are suitably aligned so they can be used in interlocked operations. Because of that, records can implement atomic Increment, Decrement, Add, and Substract operations.

Reading and writing values stored in the record (through the Value property or by using a supplied Implicit operator) is also atomic on the Win64 platform. On the Win32 platform, only the value of the TOmniAlignedInt32 can be accessed atomically.

 1 type
 2   TOmniAlignedInt32 = record
 3   public
 4     procedure Initialize; inline;
 5     function  Add(value: integer): integer; inline;
 6     function  Addr: PInteger; inline;
 7     function  CAS(oldValue, newValue: integer): boolean;
 8     function  Decrement: integer; overload; inline;
 9     function  Decrement(value: integer): integer; overload; inline;
10     function  Increment: integer; overload; inline;
11     function  Increment(value: integer): integer; overload; inline;
12     function  Subtract(value: integer): integer; inline;
13     class operator Add(const ai: TOmniAlignedInt32; i: integer): cardinal; inline;
14     class operator Equal(const ai: TOmniAlignedInt32; i: integer): boolean; inline;
15     class operator GreaterThan(const ai: TOmniAlignedInt32; i: integer): boolean; inline;
16     class operator GreaterThanOrEqual(const ai: TOmniAlignedInt32; i: integer): boolean; i\
17 nline;
18     class operator Implicit(const ai: TOmniAlignedInt32): integer; inline;
19     class operator Implicit(const ai: TOmniAlignedInt32): cardinal; inline;
20     class operator Implicit(const ai: TOmniAlignedInt32): PInteger; inline;
21     class operator LessThan(const ai: TOmniAlignedInt32; i: integer): boolean; inline;
22     class operator LessThanOrEqual(const ai: TOmniAlignedInt32; i: integer): boolean; inli\
23 ne;
24     class operator NotEqual(const ai: TOmniAlignedInt32; i: integer): boolean; inline;
25     class operator Subtract(ai: TOmniAlignedInt32; i: integer): cardinal; inline;
26     property Value: integer read GetValue write SetValue;
27   end; 
28 
29   TOmniAlignedInt64 = record
30   public
31     procedure Initialize; inline;
32     function  Add(value: int64): int64; inline;
33     function  Addr: PInt64; inline;
34     function  CAS(oldValue, newValue: int64): boolean;
35     function  Decrement: int64; overload; inline;
36     function  Decrement(value: int64): int64; overload; inline;
37     function  Increment: int64; overload; inline;
38     function  Increment(value: int64): int64; overload; inline;
39     function  Subtract(value: int64): int64; inline;
40     property Value: int64 read GetValue write SetValue;
41   end;

5.5 TOmniRecordWrapper

The TOmniRecordWrapper<T> class allows you to wrap any record inside an instance of a class.

1 type
2   TOmniRecordWrapper<T> = class
3   public
4     constructor Create(const value: T);
5     function  GetRecord: T;
6     procedure SetRecord(const value: T);
7     property Value: T read GetRecord write SetRecord;
8   end;

You can then use the resulting object in situations where an object is expected (for example, you can store such object in a TObjectList).

This class is used in TOmniValue to implement functions FromRecord<T> and ToRecord<T>.

5.6 TOmniRecord

The TOmniRecord<T> record allows you to wrap any value inside a record type.

1 type
2   TOmniRecord<T> = record
3   strict private
4     FValue: T;
5   public
6     constructor Create(const aValue: T);
7     property Value: T read FValue write FValue;
8   end;

5.7 IOmniAutoDestroyObject

You can use the CreateAutoDestroyObject function (OtlCommon unit) to wrap any object into an IOmniAutoDestroyObject interface.

When this interface’s reference count drops to 0, it automatically destroys the wrapped object.

1 type
2   IOmniAutoDestroyObject = interface
3     property Value: TObject read GetValue;
4   end;
5   
6   function  CreateAutoDestroyObject(obj: TObject): IOmniAutoDestroyObject;

Original object can be accessed through the Value property.

5.8 IOmniIntegerSet

The IOmniIntegerSet interface and its implementing class TOmniIntegerSet [3.06] can be used to store a set of integers. They are defined in the OtlCommon unit.

The big difference between the Delphi built-in set type and IOmniIntegerSet is that the latter can store more than 256 elements and that they are not limited in size. Delphi’s set is, on the other hand, faster and supports set operations (union, intersection, difference).

IOmniIntegerSet cannot store negative values.

 1 type
 2   TOmniIntegerSetChangedEvent = procedure(const intSet: IOmniIntegerSet) of object;
 3   
 4   IOmniIntegerSet = interface ['{571F85D0-CFD8-40FE-8860-A8C1C932028C}']
 5     function  Add(value: integer): boolean;
 6     procedure Assign(const value: IOmniIntegerSet);
 7     procedure Clear;
 8     function  Contains(value: integer): boolean;
 9     function  Count: integer;
10     function  IsEmpty: boolean;
11     function  Remove(value: integer): boolean;
12   {$IFDEF OTL_HasArrayOfT}
13     property AsArray: TArray<integer> read GetAsArray write SetAsArray;
14   {$ENDIF OTL_HasArrayOfT}
15     property AsBits: TBits read GetAsBits write SetAsBits;
16     property AsIntArray: TIntegerDynArray read GetAsIntArray write SetAsIntArray;
17     property AsMask: int64 read GetAsMask write SetAsMask;
18     property OnChange: TOmniIntegerSetChangedEvent read GetOnChange write SetOnChange;
19     property Item[idx: integer]: integer read GetItem; default;
20   end; 

Add adds an element to the set and returns True if element was previously present in the set, False if not.

Assign assigns one set to another.

Clear removes all elements from the set.

Contains checks whether an element is present in the set.

Count returns number of elements in the set.

IsEmpty returns True when set contains no elements.

Remove removes an element from the set and returns True if element was previously present in the set, False if not.

AsArray is available only on Delphi 2010 an newer and allows the set to be accessed as a TArray<integer>.

AsBits allows the code to access the set as Delphi’s TBits class.

AsIntArray allows the code to access the set as an array of integer.

AsMask allows the code to access the set as a bitfield mask. This is possible only if all values in the set are smaller than 64.

Item[] allows the code to access the set as an indexed array of values, for example:

1 for i := 0 to omniSet.Count - 1 do
2   DoSomethingWith(omniSet[i]);

OnChange event is triggered each time the set is modified.

5.9 Environment

The OtlCommon unit implements function Environment which returns a global IOmniEnvironment singleton. This interface can be used to access information about the system, current process, and current thread.

 1 type
 2   IOmniEnvironment = interface ['{4F9594E2-8B88-483C-9616-85B50493406D}']
 3   {$IFDEF OTL_NUMASupport}
 4     property NUMANodes: IOmniNUMANodes read GetNUMANodes;
 5     property ProcessorGroups: IOmniProcessorGroups read GetProcessorGroups;
 6   {$ENDIF OTL_NUMASupport}
 7     property Process: IOmniProcessEnvironment read GetProcess;
 8     property System: IOmniSystemEnvironment read GetSystem;
 9     property Thread: IOmniThreadEnvironment read GetThread;
10   end; 
11 
12 function  Environment: IOmniEnvironment;

The System property allows you to get the number of processors (Affinity).

1 type
2   IOmniSystemEnvironment = interface ['{9BE1EFE3-4ABB-4C2F-B2A4-B014D0949FEC}']
3     property Affinity: IOmniAffinity read GetAffinity;
4   end; 

The Process property gives the programmer access to the number of processors, associated with the current process (Affinity), memory usage statistics (Memory), process priority (PriorityClass), and execution times (Times).

You can change number of cores associated with the process by using the methods of the Affinity interface. All other information is read-only.

 1 type
 2   // from DSiWin32.pas
 3   _PROCESS_MEMORY_COUNTERS = packed record
 4     cb: DWORD;
 5     PageFaultCount: DWORD;
 6     PeakWorkingSetSize: DWORD;
 7     WorkingSetSize: DWORD;
 8     QuotaPeakPagedPoolUsage: DWORD;
 9     QuotaPagedPoolUsage: DWORD;
10     QuotaPeakNonPagedPoolUsage: DWORD;
11     QuotaNonPagedPoolUsage: DWORD;
12     PagefileUsage: DWORD;
13     PeakPagefileUsage: DWORD;
14   end;
15   PROCESS_MEMORY_COUNTERS = _PROCESS_MEMORY_COUNTERS;
16   PPROCESS_MEMORY_COUNTERS = ^_PROCESS_MEMORY_COUNTERS;
17   TProcessMemoryCounters = _PROCESS_MEMORY_COUNTERS;
18   PProcessMemoryCounters = ^_PROCESS_MEMORY_COUNTERS;
19 
20   // from OtlCommon.pas
21   TOmniProcessMemoryCounters = TProcessMemoryCounters;
22   
23   TOmniProcessPriorityClass = (pcIdle, pcBelowNormal, pcNormal, 
24     pcAboveNormal, pcHigh, pcRealtime);
25 
26   TOmniProcessTimes = record
27     CreationTime: TDateTime;
28     UserTime    : int64;
29     KernelTime  : int64;
30   end;
31 
32   IOmniProcessEnvironment = interface ['{98D6BDA3-840B-4E19-B01D-633E6A239FE9}']
33     property Affinity: IOmniAffinity read GetAffinity;
34     property Memory: TOmniProcessMemoryCounters read GetMemory;
35     property PriorityClass: TOmniProcessPriorityClass read GetPriorityClass;
36     property Times: TOmniProcessTimes read GetTimes;
37   end; 

The Thread property gives the programmer access to the number of processors, associated with the current process (Affinity), and to the thread ID (ID). You can change number of cores associated with the process by using the methods of the Affinity interface.

On massively parallel systems with multiple processor groups14 you can use the GroupAffinity property to read or set group affinity for the current thread.

1 type
2   IOmniThreadEnvironment = interface ['{5C11FEC7-9FBE-423F-B30E-543C8240E3A3}']
3     property Affinity: IOmniAffinity read GetAffinity;
4     property GroupAffinity: TOmniGroupAffinity read GetGroupAffinity 
5       write SetGroupAffinity;
6     property ID: cardinal read GetID;
7   end;

The ProcessorGroups property [3.06] is available only on Delphi 2009 and newer and gives you access to the information about processor groups in the computer.

1 type
2   IOmniProcessorGroups = interface ['{B4C871C0-CF61-4BC9-98DE-87F720F51853}']
3     function  All: IOmniIntegerSet;
4     function  Count: integer;
5     function  FindGroup(groupNumber: integer): IOmniProcessorGroup;
6     function  GetEnumerator: TList<IOmniProcessorGroup>.TEnumerator;
7     property Item[idx: integer]: IOmniProcessorGroup read GetItem; default;
8   end;

All returns set of all processor group numbers.

Count returns number of processor groups in the system.

FindGroup locates a group by its number.

GetEnumerator allows you to use a for..in enumerator to access all processor groups.

Item[] returns information on a specific processor group (0 .. Count - 1).

For each processor group you can retrieve its number (GroupNumber) and processor affinity (Affinity).

1 type
2   IOmniProcessorGroup = interface ['{BCFB0AE8-A378-4A4B-AD73-EF9B59218B55}']
3     property GroupNumber: integer read GetGroupNumber;
4     property Affinity: IOmniIntegerSet read GetAffinity;
5   end;

The NUMANodes property [3.06] is available only on Delphi 2009 and newer and gives you access to the information about NUMA nodes15 in the computer.

1 type
2   IOmniNUMANodes = interface ['{4407E795-ED35-4DD1-9EC6-D59758BAF581}']
3     function  All: IOmniIntegerSet;
4     function  Count: integer;
5     function  Distance(fromNode, toNode: integer): integer;
6     function  FindNode(nodeNumber: integer): IOmniNUMANode;
7     function  GetEnumerator: TList<IOmniNUMANode>.TEnumerator;
8     property Item[idx: integer]: IOmniNUMANode read GetItem; default;
9   end;

All returns set of all NUMA node numbers in the system.

Count returns number of NUMA nodes in the system.

Distance returns relative distance between nodes.16

FindNode locates a node by its number.

GetEnumerator allows you to use a for..in enumerator to access all NUMA nodes.

Item[] returns information on a specific NUMA node (0 .. Count - 1).

For each NUMA node you can retrieve its number (NodeNumber), the number of processor group this NUMA node belongs to (GroupNumber) and processor affinity (Affinity).

1   IOmniNUMANode = interface ['{8D3B415F-8B13-4BFF-B7C7-2D0BC1705217}']
2     property NodeNumber: integer read GetNodeNumber;
3     property GroupNumber: integer read GetGroupNumber;
4     property Affinity: IOmniIntegerSet read GetAffinity;
5   end;

5.9.1 IOmniAffinity

The IOmniAffinity interface gives you a few different ways of modifying the number of processing cores, associated with the process or thread. It also allows you to read the information about processing cores in the system.

1 type
2   IOmniAffinity = interface ['{8A6DDC70-F705-4577-869B-6810E776132B}']
3     property AsString: string read GetAsString write SetAsString;
4     property Count: integer read GetCount write SetCount;
5     property CountPhysical: integer read GetCountPhysical;
6     property Mask: DSiNativeUInt read GetMask write SetMask;
7   end;

The AsString property returns active (associated) cores as a string of characters. Following ansi characters are used for cores from 0 to 63 (maximum number of cores available to a Win64 application).

0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@$

You can change associated cores by assigning to this property.

Example: The following program will force the current program to run only on cores 2, 4, 5, and 17 (provided that they exist in the system, of course).

1 Environment.Process.Affinity.AsString := '245H';

The Count property returns number of active (associated) cores. You can also assign a number to this property to change the number of associated cores. If you do that, the code uses a random number generator to select associated cores.

The CountPhysical property returns number of physical (non hyper-threaded) cores, associated with the current entity (system, process, or thread). This information is only available on Windows XP SP3 or newer. The value returned for Count will be used on older systems.

The Mask property returns a bitmask of active (associated) cores. Bit 0 represents the CPU 0 and so on. You can also change associated cores by assigning to this property.