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 TOmniValue
s 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 the 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 with no 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 they 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.
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 an 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 it’s 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 the 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 the 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 the element was previously present in the set, False
if not.
AsArray
is available only on Delphi 2010 and 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 the 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 the number of cores associated with the process by using the methods of the Affinity
interface.
On parallel systems with multiple processor groups15 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 a set of all processor group numbers.
Count
returns the 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 nodes16 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 a set of all NUMA node numbers in the system.
Count
returns the number of NUMA nodes in the system.
Distance
returns relative distance between nodes.17
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 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 the 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.