101 lines
2.9 KiB
Markdown
101 lines
2.9 KiB
Markdown
# Threading System
|
|
|
|
Source: `src/dusk/thread/`
|
|
|
|
## Platform support
|
|
|
|
Threading currently requires **pthreads** (`DUSK_THREAD_PTHREAD`). The
|
|
implementation lives in the core thread files and is guarded by that
|
|
compile-time flag -- there are no separate per-platform thread
|
|
directories.
|
|
|
|
Thread-local storage uses the `THREAD_LOCAL` macro, which maps to
|
|
`__thread` when pthreads is available. This is used by the error system
|
|
to give each thread its own `ERROR_STATE`.
|
|
|
|
## Thread lifecycle
|
|
|
|
Threads follow a strict state machine:
|
|
|
|
```
|
|
STOPPED -> STARTING -> RUNNING -> STOP_REQUESTED -> STOPPED
|
|
```
|
|
|
|
- `threadStart()` -- blocking: starts the thread and waits until it
|
|
reaches RUNNING.
|
|
- `threadStop()` -- blocking: requests stop and waits until STOPPED.
|
|
- `threadStartRequest()` -- non-blocking equivalent of `threadStart`.
|
|
- `threadStopRequest()` -- non-blocking equivalent of `threadStop`.
|
|
|
|
The thread callback polls `threadShouldStop()` to know when to exit.
|
|
Never kill a thread forcefully -- always let it stop cooperatively.
|
|
|
|
## Thread API
|
|
|
|
```c
|
|
void threadInit(thread_t *thread, errorret_t (*callback)(thread_t *t));
|
|
// Initialise; callback is the thread entry point.
|
|
|
|
errorret_t threadStart(thread_t *thread);
|
|
// Start and block until RUNNING.
|
|
|
|
errorret_t threadStop(thread_t *thread);
|
|
// Request stop, block until STOPPED.
|
|
|
|
void threadStartRequest(thread_t *thread);
|
|
void threadStopRequest(thread_t *thread);
|
|
// Non-blocking variants.
|
|
|
|
bool_t threadShouldStop(const thread_t *thread);
|
|
// Call from inside the thread callback to know when to exit.
|
|
```
|
|
|
|
## Mutex API (`threadmutex.h`)
|
|
|
|
Each `threadmutex_t` wraps a pthread mutex and a condition variable.
|
|
|
|
```c
|
|
void threadMutexInit(threadmutex_t *mutex);
|
|
void threadMutexDispose(threadmutex_t *mutex);
|
|
|
|
void threadMutexLock(threadmutex_t *mutex);
|
|
void threadMutexUnlock(threadmutex_t *mutex);
|
|
bool_t threadMutexTryLock(threadmutex_t *mutex);
|
|
// Returns true if the lock was acquired; false if already held.
|
|
|
|
void threadMutexWaitLock(threadmutex_t *mutex);
|
|
// Block until signalled (like pthread_cond_wait).
|
|
// Must be called while holding the lock.
|
|
|
|
void threadMutexSignal(threadmutex_t *mutex);
|
|
// Wake one waiter.
|
|
```
|
|
|
|
## Usage example
|
|
|
|
```c
|
|
static errorret_t workerCallback(thread_t *t) {
|
|
while(!threadShouldStop(t)) {
|
|
// do work
|
|
}
|
|
errorOk();
|
|
}
|
|
|
|
thread_t worker;
|
|
threadInit(&worker, workerCallback);
|
|
errorChain(threadStart(&worker));
|
|
// ... later ...
|
|
errorChain(threadStop(&worker));
|
|
```
|
|
|
|
## Thread safety rules
|
|
|
|
- The error system (`ERROR_STATE`) is thread-local -- each thread has
|
|
its own error state. Do not pass `errorret_t` across thread
|
|
boundaries without copying the message and lines strings first.
|
|
- Asset loading: the background thread calls `loadAsync`; the main
|
|
thread calls `loadSync`. Never call GPU or SDL functions from the
|
|
loader background thread.
|
|
- Use `assertIsMainThread()` / `assertNotMainThread()` to guard
|
|
functions that have thread affinity requirements.
|