Channels
Channels are a frontend for data exchange between instances, built on top of the memcpy
of the Core API (See Memcpy: Distributed). A channel is a global, distributed entity, which consists of at least one producer channel, and one consumer channel in its simplest form.
There are a following different channel versions implemented (each line being a version), based on the size of the elements, the type of channel (single-producer or multiple-producer at the moment), and the policy, which is currently implemented for MPSC:
Element sizes |
Channel type |
Policy |
---|---|---|
Fixed-Size |
SPSC |
|
Variable-Size |
SPSC |
|
Fixed-Size |
MPSC |
Locking |
Variable-Size |
MPSC |
Locking |
Fixed-Size |
MPSC |
Nonlocking |
Variable-Size |
MPSC |
Nonlocking |
The locking policy implies that all producers attempt to lock the same shared channel, and the nonlocking policy implies that all producers nonlockingly access dedicated channels.
Instantiate channels
Channels themselves do not allocate memory, hence the user needs to first allocate following memory slots:
Coordination buffers need to be allocated at both producer and consumer, and they need to be globally advertised
A capacity (in number of tokens), and a token size (in bytes) needs to be specified for the channel at both producer and consumer
A token buffer of the specified capacity needs to be created at the consumer side, and needs to be advertised to the producer
Communication Management has more on allocating memory slots and making them globally available.
Producer
A producer might declare a channel as follows:
// Creating producer channel
auto producer =
HiCR::channel::fixedSize::SPSC::Producer(communicationManager,
tokenBuffer,
coordinationBuffer,
consumerCoordinationBuffer,
sizeof(ELEMENT_TYPE),
channelCapacity);
Consumer
A consumer might declare a channel as follows:
// Creating consumer channel
auto consumer =
HiCR::channel::fixedSize::SPSC::Consumer(communicationManager,
globalTokenBufferSlot,
coordinationBuffer,
producerCoordinationBuffer,
sizeof(ELEMENT_TYPE),
channelCapacity);
Using channels
The channels have very few available operations:
Querying and updating the current channel size (Consumer/Producer)
Pushing a token from a memory slot into the channel (Producer)
Peeking the position of the next available entry in the channel, and popping it from the channel (Consumer)
Query and update channel utilization
Both consumer and producer might query the current channel utilization. E.g. a consumer might busy wait on receiving a token as follows:
while (consumer.getDepth() < 1) consumer.updateDepth();
Send element
A producer can push tokens into a channel, from example using a local memory slot:
// Allocating a send slot to put the values we want to communicate
ELEMENT_TYPE sendBuffer = 42;
auto sendBufferPtr = &sendBuffer;
auto sendSlot = memoryManager.registerLocalMemorySlot(bufferMemorySpace, sendBufferPtr, sizeof(ELEMENT_TYPE));
producer.push(sendSlot);
Receive element
A consumer might inspect an element by getting its position with the peek operation first, and when done, popping them from the channel:
// Getting internal pointer of the token buffer slot
auto tokenBuffer = (ELEMENT_TYPE *)tokenBufferSlot->getPointer();
printf("Received Value: %u\n", tokenBuffer[consumer.peek()]);
consumer.pop();
Note
For locking channels, such as the locking MPSC, push and pop have a special semantics. Instead of returning void, they return a boolean, which returns true/false depending on the success status of the operation on the limited shared resource. In this case, a busy waiting loop with push/pop is more sensible.
API reference available: Doxygen