Tasking Overview

Overview

In a world of increasing multi-processing computing, it is important to take advantage of additional CPUs to perform parallel work. However, creating threads is tedious and individual threads have a cost to start and tear down. It is therefore highly recommended to create a pool of threads to perform a vast array of small individual tasks. Enter the carb.tasking plugin.

Quick Start

The carb.tasking plugin is very powerful, but can be very simple to use. Similar to std::thread, nearly all of the carb::tasking::ITasking::addTask() variant functions support std::invoke-style bind parameters. They can be given functors, function pointers, member-function pointers, or lambdas. Here is a simple example of queuing a lambda:

auto tasking = carb::getCachedInterface<carb::tasking::ITasking>();

auto future = tasking->addTask(carb::tasking::Priority::eDefault, {}, [] {
    // This runs in a fiber within a task thread!
    requestSomethingAsync();

    // Suspend ourself until a result is ready
    tasking->suspendTask();

    // Woken with wakeTask()! Return the result.

    return getAsyncResult();
});

doSomethingElse();

// Wait for the task to complete and return
auto result = future.get();

You could even create many hundreds or thousands of “persistent tasks”–tasks that run periodically, then sleep for a bit:

tasking->addTask(carb::tasking::Priority::eDefault, {}, &NPC::brain, this);

void NPC::brain()
{
    while (!done)
    {
        // Do thinking for this NPC
        think();

        // Wait until the next time we should wake up and think
        tasking->sleep_for(std::chrono::milliseconds(100));
    }
}

Architecture

The carb.tasking plugin is a fiber-based tasking system. Fibers are similar to threads in that they have a stack, but unlike threads they use cooperative multithreading. In other words, fibers are like a thread that the operating system will never run, instead a thread has to explicitly run the fiber. However, the carb.tasking scheduler does this for you.

This means that each task you add to the system has its own full stack until the task ends. This also means that tasks have a rich functionality feature-set and can do things like block on I/O or sleep without affecting a thread of execution.

This allows the operating system to run a specific number of threads (generally the number of hardware threads [i.e. CPUs] available to the system) and the carb.tasking scheduler will provide them with application-specific tasks to perform. For more information see Fibers vs. Threads and Tasking Best Practices.

graph LR task1{Task 1} --> fiber1 fiber1{Fiber} -.-> thr[ Thread Pool. ] task2{Task 2} --> fiber2 fiber2{Fiber} -.-> thr task3{Task 3} --> fiber3 fiber3{Fiber} -.-> thr classDef bold font-weight:bold,stroke-width:4px; class thr bold