6be6491c05
* comm/trackplayer Callbacks are now queued instead of asynchronously called on the stream decoder thread * libs/callback now guards the list with a mutex (called from different threads) * only one thread (Starcon2Main) is allowed to queue draw commands right now; a sanity check is in place (define DEBUG_DCQ_THREADS) Fixes: Alarms and Callbacks are used for more than just Netplay, reflect this. git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@3761 8092fc87-c524-0410-9efc-e669fe64eaf9
166 lines
6.4 KiB
Plaintext
166 lines
6.4 KiB
Plaintext
The UQM Threading and Synchronization library
|
|
|
|
This document describes the function and API for the many threading and
|
|
synchronization routines that UQM uses. People attempting to port this
|
|
to thread systems that aren't covered by SDL (such as OS 9, for
|
|
instance) will find the necessary API descriptions here.
|
|
|
|
#defines
|
|
|
|
NAMED_SYNCHRO: When #defined, all synchronization objects are named.
|
|
This should be kept on all the time, at least until 1.0.
|
|
|
|
TRACK_CONTENTION: implies NAMED_SYNCHRO. Spits out status messages
|
|
whenever a thread goes to sleep because of an object matching
|
|
TRACK_CONTENTION_CLASSES.
|
|
|
|
TRACK_CONTENTION_CLASSES: This is an ORring of enums defined in
|
|
threadlib.h.
|
|
|
|
Constructs
|
|
----------
|
|
|
|
The operation of each construct is given; these operations map to the
|
|
API in fairly straightforward ways.
|
|
|
|
The locking constructs have an argument called "sync_class"; this
|
|
indicates which value must be defined in TRACK_CONTENTION_CLASSES to
|
|
see reports from this object. Valid values are SYNC_CLASS_TOPLEVEL
|
|
for game logic synchronizers, SYNC_CLASS_AUDIO and SYNC_CLASS_VIDEO
|
|
for audio and video systems, and SYNC_CLASS_RESOURCE for low-level
|
|
resource allocators like memory systems.
|
|
|
|
- Task
|
|
|
|
This is the construct that the UQM game logic sees. In our
|
|
multithreaded code tasks map directly to threads. A task is
|
|
"assigned" to be started. One of its arguments demands that a stack
|
|
size be specified; UQM ignores this.
|
|
|
|
Tasks can voluntarily yield their timeslice by calling TaskSwitch().
|
|
This is mandatory inside busywait loops; otherwise priority inversion
|
|
problems surface under BSD and even on other systems it causes undue
|
|
CPU utilization.
|
|
|
|
Tasks have "states" that can be read or modified by various other
|
|
threads. The only state that is ever checked in the UQM code is
|
|
TASK_EXIT, which is set when the task needs to be shut down.
|
|
|
|
Task functions themselves return an integer and take a void pointer
|
|
that can be cast to "Task". This is a reference to the Task itself,
|
|
and is used to check its own state and properly de-initialize itself.
|
|
|
|
The last thing a Task function must do before exiting is call
|
|
FinishTask.
|
|
|
|
If another thread wishes to terminate a Task, it can call
|
|
"ConcludeTask" upon it. ConcludeTask will wait for the Task to
|
|
recognize the TASK_EXIT state and exit; this can produce deadlocks if
|
|
the thread requestion the Task's termination is holding a resource
|
|
that the concluding Task needs. Program with care.
|
|
|
|
API:
|
|
Task AssignTask (ThreadFunction task_func, SDWORD Stacksize,
|
|
const char *name);
|
|
DWORD Task_SetState (Task task, DWORD state_mask);
|
|
DWORD Task_ClearState (Task task, DWORD state_mask);
|
|
DWORD Task_ToggleState (Task task, DWORD state_mask);
|
|
DWORD Task_ReadState (Task task, DWORD state_mask);
|
|
void TaskSwitch (void);
|
|
void FinishTask (Task task);
|
|
void ConcludeTask (Task task);
|
|
|
|
|
|
- Thread
|
|
|
|
A somewhat more powerful and low-level construct than the Task.
|
|
Threads carry a "ThreadLocal" data object with them - at present, this
|
|
is a single Semaphore used for signaling the thread when it wants to
|
|
sleep until some graphics have been rendered. Threads can be created
|
|
with arbitrary data passed as an argument (in the form of the void *),
|
|
and can request their own ThreadLocal object. Threads can also be put
|
|
to sleep for various amounts of time. Waiting on a thread permits one
|
|
to block a thread until another completes.
|
|
|
|
API:
|
|
Thread CreateThread (ThreadFunction func, void *data,
|
|
SDWORD stackSize, const char *name);
|
|
void SleepThread (TimePeriod timePeriod);
|
|
void SleepThreadUntil (TimeCount wakeTime);
|
|
void TaskSwitch (void);
|
|
void WaitThread (Thread thread, int *status);
|
|
|
|
|
|
- Mutex
|
|
|
|
The simplest form of lock. If a thread tries to lock a mutex, it will
|
|
sleep if the mutex is already locked, and awaken once the mutex
|
|
becomes available. A Mutex must be unlocked by the same thread that
|
|
locked it, and a thread must never lock a mutex it has already locked
|
|
(without unlocking it first).
|
|
|
|
API:
|
|
Mutex CreateMutex (const char *name, DWORD syncClass);
|
|
void DestroyMutex (Mutex sem);
|
|
void LockMutex (Mutex sem);
|
|
void UnlockMutex (Mutex sem);
|
|
|
|
- RecursiveMutex
|
|
|
|
A somewhat more powerful version of the Mutex; this mutex may be
|
|
locked multiple times by the same thread, but then to truly release it
|
|
it must unlock it an equal number of times. The Draw Command Queue
|
|
(DCQ) is protected by a recursive mutex. (This construct is
|
|
occasionally called a "reentrant mutex.")
|
|
|
|
API:
|
|
RecursiveMutex CreateRecursiveMutex (const char *name,
|
|
DWORD syncClass);
|
|
void DestroyRecursiveMutex (RecursiveMutex m);
|
|
void LockRecursiveMutex (RecursiveMutex m);
|
|
void UnlockRecursiveMutex (RecursiveMutex m);
|
|
int GetRecursiveMutexDepth (RecursiveMutex m);
|
|
|
|
- Semaphore
|
|
|
|
Semaphores superficially resemble Mutexes; they are "set" and
|
|
"cleared". An integer is associated with each semaphore. Clearing
|
|
the semaphore raises the value by one; setting it decreases it by one.
|
|
Attempting to set a semaphore that is at value 0 will sleep the thread
|
|
until the semaphore is cleared.
|
|
|
|
Semaphores are used in the code to sleep until another thread is
|
|
finished doing something. They are used heavily by the FlushGraphics
|
|
code and the thread handling routines. A Task in the game is
|
|
dedicated to advancing the calendar when necessary; this task is put
|
|
to sleep by the clock semaphore when the game is paused, the player
|
|
goes into orbit or to the menu, or any of several other events that
|
|
act to suspend the game clock.
|
|
|
|
API:
|
|
Semaphore CreateSemaphore (DWORD initial, const char *name,
|
|
DWORD syncClass);
|
|
void DestroySemaphore (Semaphore sem);
|
|
void SetSemaphore (Semaphore sem);
|
|
void ClearSemaphore (Semaphore sem);
|
|
|
|
- CondVar
|
|
|
|
If a thread waits on a condition variable, it will go to sleep until
|
|
some other thread tells that condition variable to signal a thread (or
|
|
broadcast to all threads) that are waiting upon that variable. Our
|
|
condition variables are weaker than the "standard" variables.
|
|
Condition variables are generally used in conjunction with mutexes,
|
|
but ours do not permit this synchronization and are only suitable for
|
|
use if the condition variable will broadcast periodically. UQM has a
|
|
condition variable associated with the DCQ that broadcasts whenever
|
|
the queue is empty. Threads that attempt to write to the DCQ when the
|
|
DCQ is full wait on this condition variable.
|
|
|
|
API:
|
|
CondVar CreateCondVar (const char *name, DWORD syncClass);
|
|
void DestroyCondVar (CondVar);
|
|
void WaitCondVar (CondVar);
|
|
void SignalCondVar (CondVar);
|
|
void BroadcastCondVar (CondVar);
|