From bee3360bf27f7f7f5c9f795b480793c54b516d17 Mon Sep 17 00:00:00 2001 From: mcmartin Date: Sun, 12 Oct 2003 07:54:06 +0000 Subject: [PATCH] First complete edition git-svn-id: svn://svn.code.sf.net/p/sc2/code/trunk@1271 8092fc87-c524-0410-9efc-e669fe64eaf9 --- sc2/doc/devel/threads | 147 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 6 deletions(-) diff --git a/sc2/doc/devel/threads b/sc2/doc/devel/threads index 972766211..65147a3ce 100644 --- a/sc2/doc/devel/threads +++ b/sc2/doc/devel/threads @@ -1,8 +1,9 @@ The UQM Threading and Synchronization library -This document is woefully incomplete at the moment. It will be filled -in later. It's just that the ChangeLog refers to it so I want the -file to be here. --Michael +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 @@ -17,17 +18,151 @@ 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 -- ThreadLocal +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 -- Semaphore +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). The most important Mutex in the program +the GraphicsLock. Code in UQM that does things like change the +screen's clipping rectangle always grabs the GraphicsLock first, to +ensure that the screen doesn't go crazy. -- CondVar +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);