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.
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.
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 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.
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).
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.
The TOmniRecordWrapper<T>
class allows you to wrap any record inside an
instance of a class.
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.
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.
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.
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:
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.
The System
property allows you to get the number of processors (Affinity
).
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.
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.
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.
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
).
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.
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
).
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.
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).
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.