Skip to content

Threading

Romain Milbert edited this page Nov 1, 2022 · 8 revisions

Thread pool

A thread pool object can be used to execute actions on different threads. The concept of a thread pool is a collection of threads running endlessly, to which you can give actions to execute.

⚠️ Do keep in mind that an action pushed to a thread pool is not blocking, meaning that concurrency issues can arise if care is not taken.

#include <RaZ/Utils/ThreadPool.hpp>

// A thread pool can be created by giving a specific number of threads, or nothing to get the default amount
Raz::ThreadPool threadPool; // Equivalent to Raz::ThreadPool threadPool(Raz::Threading::getSystemThreadCount());

// Actions can be pushed to the pool using the addAction() member function:

int i = 0;
threadPool.addAction([&i] () { i = 42; });
// The variable i will be equal to 42 anytime. However, since it is asynchronous, there is no guarantee
//  that the value will immediately be set, so using it directly after this call may provoke a data race

threadPool.addAction([&i] () { i = 3; });
// For the same reason, this other function is dangerous: nothing requires that i will be set first to 42,
//  then to 3. Specific action must be taken, like setting up a mutex or making i an atomic variable

A "default" thread pool, created with the default thread count & used for other parallel algorithms, is available through Raz::Threading::getDefaultThreadPool().

Parallelization

Actions can be performed by multiple threads, either individually or over separate ranges of any collection:

#include <RaZ/Utils/Threading.hpp>

// Executing an action on 4 different threads. Be careful about data concurrency!
Raz::Threading::parallelize([] () { ... }, 4);

std::array<int, 5> values = { 1, 2, 3, 4, 5 };

// Incrementing all elements in the array on multiple threads
// No overlap between values is created, making this function thread safe for the given collection
// This of course works even when giving a number of threads not divisible by the collection's size
Raz::Threading::parallelize(0, values.size(), [&values] (const Raz::Threading::IndexRange& range) {
    for (std::size_t i = range.beginIndex; i < range.endIndex; ++i)
        ++values[i];
}, 4);

// You can also give two iterators to iterate over. In the example below, 'values' could have
//   been strictly replaced by 'values.begin(), values.end()'
// Strongly consider the use of 'const auto& range' instead of specifying the whole type
Raz::Threading::parallelize(values, [] (const Raz::Threading::IterRange<std::array<int, 5>::iterator>& range) {
    for (int& val : range)
        ++val;
}, 4);

These functions block the main thread before exiting, waiting for all threads to finish their tasks, so that you don't have to.

Clone this wiki locally