Tasking

The tasking frontend contains building blocks to develop a task-based runtime system:

  • task : The tasks to be executed

  • callbackMap: Map that relates task-related callbacks

  • worker : A worker takes on tasks to execute

Task and CallbackMap

The tasks are built upon HiCR execution states.

Task creation

To create and initialize a task one has to choose one of our compute managers (e.g. Pthreads). This compute manager is then used to construct an execution unit which holds the function we want to execute.

// Choose a compute manager (e.g. Pthreads)
HiCR::backend::pthreads::ComputeManager cm;

// Initialize the execution unit with the function (e.g. Boost)
auto executionUnit = cm.createExecutionUnit([](){ printf("Hello, I am a Task\n"); });

// Create a task
auto task = Task(executionUnit);

Task run and suspend

After that, one has to create an execution state with the compute manager and run the task

// Initialize the execution state with the execution unit
auto executionState = cm.createExecutionState(executionUnit);

// Initialize the task with the execution state
task.initialize(executionState);

// Run the task
task.run();

// This function yields the execution of the task (if supported), and returns to the worker's context
task.suspend()

Additionally, It provides basic support for stateful tasks with settable callbacks to notify when the task changes state:

  • onTaskExecute: Triggered as the task starts or resumes execution

  • onTaskSuspend: Triggered as the task is preempted into suspension

  • onTaskFinish : Triggered as the task finishes execution

  • onTaskSync : Triggered as the task receives a sync signal (used for mutual exclusion mechanisms)

The CallbackMap class maps the state of any classes (e.g. task) to their corresponding callback.

Worker

The workers are built upon HiCR processing units. The worker class also contains a simple loop that calls a user-defined scheduling function. The function returns the next task to execute.

Worker creation

Instead of running a task directly (see Task run and suspend), one can create a worker running those tasks. This time we will use the nOS-V backend. After the creation, we need to add processing units to the tasks.

// Initialize the two compute managers
HiCR::backend::nosv::ComputeManager processingUnitComputeManager;
HiCR::backend::nosv::ComputeManager executionStateComputeManager;

// Create the worker
auto worker = Worker(&executionStateComputeManager, &processingUnitComputeManager);

// Creating a Processing Unit of the first available computeResource
auto processingUnit = processingUnitComputeManager.createProcessingUnit(computeResources[0]);

// Add the PU to the worker
worker.addProcessingUnit(processingUnit)

This worker is now ready to get tasks.

Worker usage

To use a worker, the following lifecycle should be followed:

// Initializes the worker and its resources
worker.initialize();

// Initializes the worker's task execution loop
worker.start();

// Suspends the execution of the underlying resource(s)
worker.suspend();

// Terminates the worker's task execution loop
worker.terminate();

// A function that will suspend the execution of the caller until the worker has stopped
worker.await();

Additionally, these are the following states a worker can be:

  • uninitialized: The worker object has been instantiated but not initialized

  • ready : The worker has been ininitalized (or is back from executing) and can currently run

  • running : The worker has started executing

  • suspending : The worker is in the process of being suspended

  • suspended : The worker has suspended

  • resuming : The worker is in the process of being resumed

  • terminating : The worker has been issued for termination (but still running)

  • terminated : The worker has terminated

API reference available: Doxygen