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.
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.
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. If the value is not found, the function returns -1.
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.
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
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.
Those two records hold 4-byte (32 bit) and 8-byte (64 bit) values, respectively.
These values are suitably aligned so it can be read from and written to in an atomic operation. They also 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;
17 inline;
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;
23 inline;
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;
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>.
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;
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.
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
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.
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
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
3 property Affinity: IOmniAffinity read GetAffinity;
4 end;
The Process property gives you 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
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 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
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
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
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
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
2 property NodeNumber: integer read GetNodeNumber;
3 property GroupNumber: integer read GetGroupNumber;
4 property Affinity: IOmniIntegerSet read GetAffinity;
5 end;
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
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. On all other platforms it will return the same value as the Count property.
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.