Linux Game Programming for PC & Embedded Systems using SDL
Presented by
Fore June
Author of Windows Fan, Linux Fan

Games and SDL
SDL Installation
SDL for Embedded
SDL API
SDL Events
 SDL Graphics
SDL Threads
Thread Example
SDL Animation
SDL Sound
 Raw Video Player
Video Formats
Video Compression
 Game Trees
About The Author

An Introduction to Video Compression in C/C++ now available at Amazon

@Copyright by Fore June, 2006
SDL Threads
1 2 3

  1. Thread Synchronization
  2. So far, we have discussed how to create SDL threads and use them to do some independent activities, which can be coordinated using SDL_WaitThread(). If the threads are working on activities that are not independent of each other, we need more sophisticated coordination. This is because threads that run simultaneously can interfere with each other when they access the same area of memory. Consider, for instance, we have two threads, namely, Writer and Reader. Writer updates two variables, account_value and total; whatever has been changed to account_value should be reflected in total. Suppose at a certain point, their values are:

    At some point, Writer has subtracted 50 from account_value and is about to update total. However, at this instance, Reader reads the values. She will find that the values become:

    and find inconsistency between the values. The inconsistency arises because Reader interferes with Writer's updating activity. To avoid the inconsistency, the update should be done atomically, which means indivisibly. That is, no interference of other threads is allowed until the update activity has been finished. The piece of code that needs to be executed as a whole is often referred to as a critical section.

    Consistency can be accomplished by using a mutex, which actually means mutual exclusion. A mutex is a data structure which provides a "locking" mechanism that allows only one thread at a time to execute a section of code. This is in analogy with the case when you use the rest room in a small fast food restaurant. When you use it, you have to first lock it to prohibit other customers to enter. When you are done, you will unlock it. A mutex has to be created before using and destroyed after all threads finished using it. SDL provides four functions for mutex operations:

    1. SDL_CreateMutex() - Creates a mutex
    2. SDL_DestroyMutex() - Destroys a mutex
    3. SDL_mutexP() - Locks a mutex
    4. SDL_mutexV() - Unlocks a mutex

    The first two are self-explained. The last two, ending with P and V require some explanations. Actually, mutexes are special forms of semaphores, first used by E. W. Dijkstra, a professor in the Department of Mathematics at the Technological University, Eindhoven, Netherlands, to do synchronization in computer programs. A counting semaphore ( aka PV semaphore ) consists of a variable that can be incremented to a very large value, but decremented only to zero. A V operation ( aka "V" - verhogen in Dutch ) increments the semaphore value, while a P operation ( aka "P" - Proberen te verlagen ) tries to decrement it. Many textbooks in computer science name the P and V opeations as Down and Up or wait and signal or lock and unlock respectively. When the semaphore's ability to count is not required, its simplified version, mutex is often used. In general, a mutex has two two states: unlocked or locked. When a thread needs access to a critical section, it calls mutex_lock(). If the mutex is currently unlocked, meaning that the critical section is not used, the call succeeds and the calling thread enters the critical section. On the other hand, if the mutex is already locked, the calling thread is blocked until the thread executing the critical section is finished and calls mutex_unlock(). Mutexes are good only for managing mutual exclusion for some shared resources or piece of code; they are easy and efficient to implement, which makes them especially useful in thread packages that are implemented entirely in user space.

    When a thread executes P() ( wait ) to enter a critical section, if the mutex ( semaphore ) value is nonzero, it will be decremented by one and the operation succeeds; if not, then the calling thread must go to sleep until a different thread incremnets it; if more than one thread has executed P() while its value is zero, then the threads will be put in a sleeping queue. When a thread has finished the critical section, it must execute V() ( signal() ) to wake up a thread in the sleeping queue.

    The following program, sync0.cpp implements the Reader/Writer example we discussed above. The mutex value_mutex is used to ensure the accessing of the shared variables, account_value and total is done atomically. Before accessing the shared variables, SDL_mutexV ( value_mutex ) is called; if another thread is accessing it, the thread is blocked ( put to sleep ). When finished accessing the variables, SDL_mutexP ( value_mutex ) is called to 'unlock' the state and wake up a sleeping thread if there is any.

    When you execute the above program sync0, you might notice that the shared variables can be updated more than once before they are read and vice versa. Now suppose we require that the variables be updated only if their values have been read and vice versa. That is, the read and write process must be done alternatively. Can we accomplish this using the mutex operations? Though this can be done using POSIX mutexes, this basically cannot be done using SDL mutexes alone. This is because the P ( lock ) operation of SDL is implemented differently from that of Pthreads. The SDL_mutexP() operation is implemented in a way that it allows re-entrance. That is, even if the mutex value has been decremented to 0, the thread will not be blocked as long as the value was previously decremented to 0 by the same thread. Because of this special property, a thread cannot use an SDL mutex to block itself to achieve strict alternation.

    Therefore, we need to use some other methods to accomplish strict alternation. A simple way to do this is to use another variable say, turn, to guarantee the threads take turns to enter the critical section. For example, when turn = 1, only thread 1 is allowed to enter; when turn = 2, thread 2 is allowed, and so on. The following code uses the variable value_consumed to enforce the writer and reader threads to take turns to access the shared variables.

    In program sync1.cpp, synchronization between reader and write is enforced by the variable value_consumed. Each thread has to check value_consumed to see if its her turn to access variables account_value and total. If not, she has to sleep for 20 milli-seconds ( SDL_Delay ( 20 ); ) and check again. This is not very desirable as she might have to keep waking up to check her turn and go back to sleep until her turn has reached. This behavior is often referred to as busy waiting. Wouldn't it be nice, if the thread could sleep all the way until her turn comes and someone else wakes her up? Indeed, this is a preferable way and can be done using SDL semaphores.

  3. Semaphores
  4. We have discussed semaphores briefly in the above section. In SDL, a semaphore is a generalization of a mutex with more functions and flexibilities. Though semaphores are primitive and simple, they can be used to solve many synchronization problems. As you'll see, the above Reader/Writer problem with strict alternation constraint can easily be handled using two semaphores.

    SDL provides a few basic functions of semaphore operations:

    1. SDL_CreateSemaphore() - Creates a new semaphore and assigns an initial value to it.
    2. SDL_DestroySemaphore() - Destroys a semaphore that was created by SDL_CreateSemaphore().
    3. SDL_SemWait() - Locks a semaphore and suspend the thread if the semaphore value is zero.
    4. SDL_SemTryWait() - Attempts to lock a semaphore but do not suspend the thread.
    5. SDL_SemWaitTimeout() - Locks a semaphore, but only waits up to a specified maximum amount of time.
    6. SDL_SemPost() - Unlocks a semaphore.
    7. SDL_SemValue() - Returns the current value of a semaphore.

    The following program sync2.cpp demonstrates the use of SDL semaphores to coordinate two threads to access a common value alternatively. Two semaphore variables, read_sem, and write_sem are used to synchronize the threads. At the instance when they are created, read_sem is initialized to a value of 0 and write_sem to 1.

    The reader thread waits on read_sem to enter the critical section ( to access value ). If the value of read_sem is not 0, it decrements it and proceeds to execute the critical section, otherwise it goes to sleep. As its initial value is 0, reader has to wait until writer has increased read_sem value to 1. Upon exiting the critical section ( i.e. finished reading the value ), reader executes 'SDL_SemPost ( write_sem )' to increment write_sem and wakes up writer if writer is sleeping.

    On the other hand, the writer thread waits on write_sem. As its initial value is 1, at the beginning, it decrements it to zero and proceeds to execute the critical section ( writing value ). Upon exiting the critical section, it executes 'SDL_SemPost ( read_sem )' to increment read_sem and wakes up reader. In the next round, it will wait on write_sem which is 0 unless the reader thread has read value and executed 'SDL_SemPost ( write_sem )' and so on. So the writer thread and the reader thread will access value alternatively.

    Semaphores are primitive methods to synchronize threads. A number of things could go wrong when you use semaphores to coordinate the interactions of threads. Even if you have carefully locked all shared variable, other unexpected problems may still arise. One common problem you may encounter is deadlock, which will be discussed in next section. In general, all synchronization problems have a solution though not perfect.

    Next