[This documentation is for preview only, and is subject to change in later releases. Blank topics are included as placeholders.]
The Agents Library provides several message-block types that let you propagate messages among application components in a thread-safe manner. These message-block types are often used with the various message-passing routines. For more information about the message passing routines that are defined by the Agents Library, see Message Passing Functions.
This topic contains the following sections:
Sources and targets are two important participants in message passing. A source refers to an endpoint of communication that sends messages. A target refers to an endpoint of communication that receives messages. You can think of a source as an endpoint that you read from and a target as an endpoint that you write to. Applications connect sources and targets together to form messaging networks.
The Agents Library uses two abstract classes to represent sources and targets: ISource and ITarget. Message block types that act as sources derive from ISource; message block types that act as targets derive from ITarget.
The topic Message Passing Functions describes message passing functions in more detail.
[go to top]
Overview of Message Block Types
The following table briefly describes the role of the important message-block types.
- unbounded_buffer
Stores a queue of messages.
- overwrite_buffer
Stores one message that can be written to and read from multiple times.
- single_assignment
Stores one message that can be written to one time and read from multiple times.
- call
Performs work when it receives a message.
- transformer
Performs work when it receives data and sends the result of that work to another target block. The transformer class can act on different input and output types.
- choice
Selects the first available message from a set of sources.
- join and multitype join
Wait for all messages to be received from a set of sources and then combine the messages into one message for another message block.
- timer
Sends a message to a target block on a regular interval.
These message-block types have different characteristics that make them useful for different situations. These are some of the characteristics:
Propagation type: Whether the message block acts as a source of data, a receiver of data, or both.
Message ordering: Whether the message block maintains the original order in which messages are sent or received. Each predefined message block type maintains the original order in which it sends or receives messages.
Source count: The maximum number of sources that the message block can read from.
Target count: The maximum number of targets that the message block can write to.
The following table shows how these characteristics relate to the various message-block types.
Message block type | Propagation type (Source, Target, or Both) | Message ordering (Ordered or Unordered) | Source count | Target count |
|---|
unbounded_buffer | Both | Ordered | Unbounded | Unbounded |
overwrite_buffer | Both | Ordered | Unbounded | Unbounded |
single_assignment | Both | Ordered | Unbounded | Unbounded |
call | Target | Ordered | Unbounded | Not Applicable |
transformer | Both | Ordered | Unbounded | 1 |
choice | Both | Ordered | 10 | 1 |
join | Both | Ordered | Unbounded | 1 |
multitype_join | Both | Ordered | 10 | 1 |
timer | Source | Not Applicable | Not Applicable | 1 |
The following sections describe the message-block types in more detail.
[go to top]
The unbounded_buffer class represents a general-purpose asynchronous messaging structure. This class stores a first in, first out (FIFO) queue of messages that can be written to by multiple sources or read from by multiple targets. When a target receives a message from an unbounded_buffer object, that message is removed from the message queue. Therefore, although an unbounded_buffer object can have multiple targets, only one target will receive each message. The unbounded_buffer class is useful when you want to pass multiple messages to another component, and that component must receive each message.
Example
The overwrite_buffer class is like the unbounded_buffer class, except that an overwrite_buffer object stores just one message. In addition, when a target receives a message from an overwrite_buffer object, that message is not removed from the buffer. Therefore, multiple targets receive a copy of the message.
The overwrite_buffer class is useful when you want to pass multiple messages to another component, but that component needs only the most recent value. This class is also useful when you want to broadcast a message to multiple components.
Example
The single_assignment class resembles the overwrite_buffer class, except that a single_assignment object can be written to only one time. Like the overwrite_buffer class, when a target receives a message from a single_assignment object, that message is not removed from that object. Therefore, multiple targets receive a copy of the message. The single_assignment class is useful when you want to broadcast one message to multiple components.
Example
The call class acts as a message receiver that performs a work function when it receives data. This work function can be a lambda expression, a function object, or a function pointer. A call object behaves differently than an ordinary function call because it acts in parallel to other components that send messages to it. If a call object is performing work when it receives a message, it adds that message to a queue. Every call object processes queued messages in the order in which they are received.
Example
The transformer class acts as both a message receiver and as a message sender. The transformer class is like the call class because it performs a user-defined work function when it receives data. However, the transformer class also sends the result of the work function to receiver objects. Like a call object, a transformer object acts in parallel to other components that send messages to it. If a transformer object is performing work when it receives a message, it adds that message to a queue. Every transformer object processes its queued messages in the order in which they are received.
The transformer class sends its message to one target. If you set the _PTarget parameter in the constructor to NULL, you can later specify the target by calling the link_target method.
Unlike all other asynchronous message block types that are provided by the Agents Library, the transformer class can act on different input and output types. This ability to transform data from one type to another makes the transformer class a key component in many concurrent networks. In addition, you can add more fine-grained parallel functionality in the work function of a transformer object.
Example
The choice class selects the first available message from a set of sources. The choice class represents a control-flow mechanism instead of a dataflow mechanism (the topic Asynchronous Agents Library describes the differences between dataflow and control-flow).
Reading from a choice object resembles calling the Platform SDK WaitForMultipleObjects function when it has the bWaitAll parameter set to FALSE. However, the choice class binds data to the event itself instead of to an external synchronization object.
Typically, you use the choice class together with the receive function to drive control-flow in your application. Use the choice class when you have to select among message buffers that have different types. Use the single_assignment class when you have to select among message buffers that have the same type.
The order in which you link sources to a choice object is important because it can determine which message is selected. For example, consider the case where you link multiple message buffers that already contain a message to a choice object. The choice object selects the message from the first source that it is linked to. After you link all sources, the choice object preserves the order in which each source receives a message.
Example
join and multitype_join Classes
The join and multitype_join classes let you wait for each member of a set of sources to receive a message. The join class acts on source objects that have a common message type. The multitype_join class acts on source objects that can have different message types.
Reading from a join or multitype_join object resembles calling the Platform SDK WaitForMultipleObjects function when it has the bWaitAll parameter set to TRUE. However, just like a choice object, join and multitype_join objects use an event mechanism that binds data to the event itself instead of to an external synchronization object.
Reading from a join object produces a vector object. Reading from a multitype_join object produces a tuple object. Elements appear in these objects in the same order as their corresponding source buffers are linked to the join or multitype_join object.
Greedy Versus Non-Greedy Joins
The join and multitype_join classes support the concept of greedy and non-greedy joins. A greedy join accepts a message from each of its sources as messages become available until all message are available. A non-greedy join receives messages in two phases. First, a non-greedy join waits until all incoming messages from all sources are available. Second, after all source messages are available, a non-greedy join object accepts the message from each of its sources. If any source no longer holds a message, the non-greedy join object releases all messages that it already received and again waits for all sources to have an available message.
Greedy joins perform better than non-greedy joins because they accept messages immediately. However, greedy joins can lead to deadlocks. Use a non-greedy join when you have multiple joins that contain one or more shared source objects.
Example
The timer class acts as a message source. A timer object sends a message to a target after a specified period of time has elapsed. The timer class is useful when you must delay sending a message or you want to send a message at a regular interval.
The timer class sends its message to just one target. If you set the _PTarget parameter in the constructor to NULL, you can later specify the target by calling the link_target method.
A timer object can be repeating or non-repeating. To create a repeating timer, pass true for the _Repeating parameter when you call the constructor. Otherwise, pass false for the _Repeating parameter to create a non-repeating timer. If the timer is repeating, it sends the same message to its target after each interval.
The Agents Library creates timer objects in the non-started state. To start a timer object, call the timer::start method. To stop a timer object, destroy the object or call the timer::stop method. To pause a repeating timer, call the timer::pause method.
Example
Concepts
Other Resources