Sane C++ Libraries
C++ Platform Abstraction Libraries
Threading

🟥 Atomic, thread, thread pool, mutex, condition variable

Threading is a library defining basic primitives for user-space threading and synchronization.

Features

Class Description
SC::Thread A native OS thread.
SC::ThreadPool Simple thread pool that executes tasks in a fixed number of worker threads.
SC::Mutex A native OS mutex to synchronize access to shared resources.
SC::ConditionVariable A native OS condition variable.
SC::Atomic Atomic variables (only for int and bool for now).
SC::EventObject An automatically reset event object to synchronize two threads.

Status

🟥 Draft
Only the features needed for other libraries have been implemented so far. The Atomic header is really only being implemented for a few data types and needs some love to extend and improve it.

Description

SC::Thread

A native OS thread. Example:

Thread thread;
thread.start([](Thread& thread)
{
// It's highly recommended setting a name for the thread
thread.setThreadName(SC_NATIVE_STR("My Thread"));
// Do something on the thread
Thread::Sleep(1000); // Sleep for 1 second
});
thread.join(); // wait until thread has finished executing
// ...or
thread.detach(); // To keep thread running after Thread destructor
Warning
Thread destructor will assert if SC::Thread::detach() or SC::Thread::join() has not been called.

SC::ThreadPool

Simple thread pool that executes tasks in a fixed number of worker threads. This class is not copyable / moveable due to it containing Mutex and Condition variable. Additionally, this class does not allocate any memory by itself, and expects the caller to supply SC::ThreadPool::Task objects.

Warning
The caller is responsible of keeping Task address stable until the it will be completed. If it's not already completed the task must still be valid during ThreadPool::destroy or ThreadPool destructor.

Example:

static const size_t wantedThreads = 4;
static const size_t numTasks = 100;
// 1. Create the threadpool with the wanted number of threads
SC::ThreadPool threadPool;
SC_TEST_EXPECT(threadPool.create(wantedThreads));
size_t values[numTasks];
// 2. Allocate the wanted number of tasks. Tasks memory should be valid until a task is finished.
SC::ThreadPool::Task tasks[numTasks];
for (size_t idx = 0; idx < numTasks; idx++)
{
size_t* value = values + idx;
*value = idx;
// 3. Setup the task function to execute on some random thread
tasks[idx].function = [value]()
{
if (*value % 2)
{
}
*value *= 100;
};
// 4. Queue the task in thread pool
SC_TEST_EXPECT(threadPool.queueTask(tasks[idx]));
}
// 5. [Optional] Wait for a single task
SC_TEST_EXPECT(threadPool.waitForTask(tasks[1]));
SC_TEST_EXPECT(values[1] == 100);
// 6. [Optional] Wait for all remaining tasks to be finished
// Checking Results
bool allGood = true;
for (size_t idx = 0; idx < numTasks; idx++)
{
allGood = allGood && (values[idx] == idx * 100);
}
SC_TEST_EXPECT(allGood);
// 6. [Optional] Destroy the threadpool.
// Note: destructor will wait for tasks to finish, but this avoids it from accessing invalid tasks,
// as stack objects are reclaimed in inverse declaration order
SC_TEST_EXPECT(threadPool.destroy());
#define SC_TEST_EXPECT(e)
Records a test expectation (eventually aborting or breaking o n failed test)
Definition: Testing.h:113
static void Sleep(uint32_t milliseconds)
Puts current thread to sleep.
Simple thread pool that executes tasks in a fixed number of worker threads.
Definition: ThreadPool.h:41
Result destroy()
Destroy the thread pool created previously with ThreadPool::create.
Result queueTask(Task &task)
Queue a task (that should not be already in use)
Result waitForTask(Task &task)
Blocks execution until all queued and pending tasks will be fully completed.
Result create(size_t workerThreads)
Create a thread pool with the requested number of worker threads.
Result waitForAllTasks()
Blocks execution until all queued and pending tasks will be fully completed.
A small task containing a function to execute that can be queued in the thread pool.
Definition: ThreadPool.h:19
Function< void()> function
Function that will be executed during the task.
Definition: ThreadPool.h:21

SC::Mutex

A native OS mutex to synchronize access to shared resources. Example:

Mutex mutex;
int globalVariable = 0;
Thread thread1;
auto thread1Func = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Thread1"));
mutex.lock();
globalVariable++;
mutex.unlock();
};
SC_TEST_EXPECT(thread1.start(thread1Func));
Thread thread2;
auto thread2Func = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Signaling2"));
mutex.lock();
globalVariable++;
mutex.unlock();
};
SC_TEST_EXPECT(thread2.start(thread2Func));
SC_TEST_EXPECT(thread1.join());
SC_TEST_EXPECT(thread2.join());
SC_TEST_EXPECT(globalVariable == 2);

SC::EventObject

An automatically reset event object to synchronize two threads.
Example:

EventObject eventObject;
Thread threadWaiting;
auto waitingFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Thread waiting"));
eventObject.wait();
report.console.printLine("After waiting");
};
SC_TEST_EXPECT(threadWaiting.start(waitingFunc));
Thread threadSignaling;
auto signalingFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Signaling thread"));
report.console.printLine("Signal");
eventObject.signal();
};
SC_TEST_EXPECT(threadSignaling.start(signalingFunc));
SC_TEST_EXPECT(threadWaiting.join());
SC_TEST_EXPECT(threadSignaling.join());
// Prints:
// Signal
// After waiting

SC::Atomic

Atomic variables (only for int and bool for now).
Example:

Atomic<bool> test = true;
SC_TEST_EXPECT(test.load());
test.exchange(false);
SC_TEST_EXPECT(not test.load());

Roadmap

🟨 MVP

  • Scoped Lock / Unlock

🟩 Usable

  • Semaphores

🟦 Complete Features:

  • Support more types in Atomic<T>
  • ReadWrite Lock
  • Barrier