Sane C++ Libraries: Threading (original) (raw)
🟩 Atomic, thread, thread pool, mutex, semaphore, barrier, rw-lock, condition variable
SaneCppThreading.h is a library defining basic primitives for user-space threading and synchronization.
Dependencies
- Dependencies: Foundation
- All dependencies: Foundation
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();
report.console.printLine("After waiting");
};
Thread threadSignaling;
auto signalingFunc = [&](Thread& thread)
{
thread.setThreadName(SC_NATIVE_STR("Signaling thread"));
report.console.printLine("Signal");
eventObject.signal();
};
SC::Atomic
Atomic variables (only for int and bool for now).
Example:
Atomic 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:159
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++;
SC_TEST_EXPECT(ctx.sharedResource <= maxResources);
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 uint32_t numThreads = 8;
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
Statistics
| Type | Lines Of Code | Comments | Sum |
|---|---|---|---|
| Headers | 407 | 249 | 656 |
| Sources | 917 | 168 | 1085 |
| Sum | 1324 | 417 | 1741 |