🟩 Atomic, thread, thread pool, mutex, semaphore, barrier, rw-lock, condition variable
Threading is a library defining basic primitives for user-space threading and synchronization.
Dependencies
Statistics
Type | Lines Of Code | Comments | Sum |
Headers | 407 | 249 | 656 |
Sources | 917 | 168 | 1085 |
Sum | 1324 | 417 | 1741 |
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::RWLock | A Read-Write lock that allows multiple concurrent readers but only one writer. |
SC::Barrier | A synchronization point that blocks threads until the required number of threads have reached it. |
SC::Semaphore | A semaphore synchronization primitive that maintains a count for resource management. |
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
🟩 Usable
All the main threading primitives are there.
The Atomic header is really only being implemented for a few data types and needs some love to extend and improve it.
Blog
Some relevant blog posts are:
Videos
This is the list of videos that have been recorded showing some of the internal thoughts that have been going into this library:
SC::Thread
A native OS thread.
Example:
Thread thread;
thread.start([](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("My Thread"));
Thread::Sleep(1000);
});
thread.join();
thread.detach();
- 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;
size_t values[numTasks];
for (size_t idx = 0; idx < numTasks; idx++)
{
size_t* value = values + idx;
*value = idx;
{
if (*value % 2)
{
}
*value *= 100;
};
}
bool allGood = true;
for (size_t idx = 0; idx < numTasks; idx++)
{
allGood = allGood && (values[idx] == idx * 100);
}
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();
};
Thread thread2;
auto thread2Func = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Signaling2"));
mutex.lock();
globalVariable++;
mutex.unlock();
};
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();
};
Thread threadSignaling;
auto signalingFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Signaling thread"));
eventObject.signal();
};
SC::Atomic
Atomic variables (only for int
and bool
for now).
Example:
Atomic<bool> test = true;
test.exchange(false);
#define SC_TEST_EXPECT(e)
Records a test expectation (eventually aborting or breaking o n failed test)
Definition Testing.h:119
SC::Semaphore
A semaphore synchronization primitive that maintains a count for resource management.
Example:
constexpr int maxResources = 2;
constexpr int numThreads = 4;
constexpr int operationsPerThread = 3;
Semaphore semaphore(maxResources);
struct Context
{
Semaphore& semaphore;
Mutex counterMutex;
int sharedResource = 0;
} ctx{semaphore, {}};
Thread threads[numThreads];
for (int i = 0; i < numThreads; i++)
{
auto threadFunc = [this, &ctx](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Worker Thread"));
for (int j = 0; j < operationsPerThread; j++)
{
ctx.semaphore.acquire();
ctx.counterMutex.lock();
ctx.sharedResource++;
Thread::Sleep(1);
ctx.sharedResource--;
ctx.counterMutex.unlock();
ctx.semaphore.release();
Thread::Sleep(1);
}
};
}
for (int i = 0; i < numThreads; i++)
{
}
SC::RWLock
A Read-Write lock that allows multiple concurrent readers but only one writer.
Example:
constexpr int numReaders = 3;
constexpr int numIterations = 100;
RWLock rwlock;
int sharedData = 0;
Thread readers[numReaders];
for (int i = 0; i < numReaders; i++)
{
auto readerFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Reader"));
for (int j = 0; j < numIterations; j++)
{
rwlock.lockRead();
volatile int value = sharedData;
(void)value;
rwlock.unlockRead();
Thread::Sleep(1);
}
};
}
Thread writer;
auto writerFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Writer"));
for (int i = 0; i < numIterations; i++)
{
rwlock.lockWrite();
sharedData++;
rwlock.unlockWrite();
Thread::Sleep(1);
}
};
for (int i = 0; i < numReaders; i++)
{
}
SC::Barrier
A synchronization point that blocks threads until the required number of threads have reached it.
Example:
constexpr int incrementsPerThread = 1000;
Thread threads[numThreads];
Barrier barrier(numThreads);
struct Context
{
Barrier& barrier;
Atomic<int32_t> sharedCounter;
} ctx = {barrier, 0};
for (uint32_t i = 0; i < numThreads; i++)
{
auto threadFunc = [this, &ctx](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Barrier"));
for (int j = 0; j < incrementsPerThread; ++j)
{
ctx.sharedCounter++;
}
ctx.barrier.wait();
SC_TEST_EXPECT(ctx.sharedCounter == numThreads * incrementsPerThread);
ctx.barrier.wait();
};
}
for (uint32_t i = 0; i < numThreads; i++)
{
}
SC::ConditionVariable
A native OS condition variable.
Roadmap
🟦 Complete Features:
- Support more types in Atomic<T>