Tips and Advice for Multithreaded Programming in SDL

This document mostly stems from the resolution of a number of problems I encountered while learning how to program with multiple threads in the Simple DirectMedia Library (SDL). The documentation that comes with the library is not very specific about how to use a number of very useful functions, so here I will try to explain the way I understand these constructs in plain English.

A simple but effective main() loop.

The SDL_PollEvent example code from the SDL manual is shown below with minor modifications in red. Beginners might be tempted to use this type of infinite while() loop in their program's main() function to handle events. I know, because I did it once. However, polling for events is a really bad way to track events.

SDL_Event event;                     /* Event structure */

while(nearly infinite loop) {
                                     /* Check for events */
    while(SDL_PollEvent(&event)) {   /* Loop until there are no events left on the queue */
        switch(event.type) {         /* Process the appropiate event type */
            case SDL_KEYDOWN:        /* Handle a KEYDOWN event */
                printf("Oh! Key press\n");
                break;
            case SDL_MOUSEMOTION:
                break;
            case SDL_QUIT:
                exit(1);
            default:                 /* Report an unhandled event */
                printf("I don't know what this event is!\n");
        }
    }
}

Remember that in most modern operating systems, every program you run is forked as an independent process, and that any ill-designed while() loops contained in your program will cause your process to run far more often than needed to actually maintain full responsiveness. The above code example alone will cause your CPU to peg out at 100% (which is what I've noticed that a lot of SDL-based programs actually *do*). Instead, it helps to think of the main() function as just another thread. You want it to sleep as much as possible.

A much better way to implement the event loop is use the SDL_WaitEvent() function. It will suspend the loop while waiting for an event to come up in the queue, and won't burn CPU cycles like polling does.

A good skeleton main() function would look something like this:

void main(void)
{
    [after SDL initialization]

    SDL_Event event;
    while (F_EVENT_LOOP == RUNNING)
    {
        if (SDL_WaitEvent(&event)) /* execution suspends here while waiting on an event */
        {
            switch (event.type)
            {
                case SDL_QUIT:
                    printf("SDL_QUIT signal received.\n");
                    F_EVENT_LOOP = STOPPED;
            }
        }
    }
}

Using Condition Variables

First. Declare pointers to a mutex and a condition variable.

    SDL_mutex *ui_initlock;
    SDL_cond *ui_initcond;

Next. Add the following line to a known point in the execution of the thread you want to control using the condition variable.

int tUIHandler(void *unused) {

    ...
    
/*  during init, suspend UI thread until user selects a packet-capture interface. */
    ui_initcond = SDL_CreateCond();
    SDL_CondWait(ui_initcond, ui_initlock);
    SDL_DestroyCond(ui_initcond);
    ui_initcond = 0;
    
    ...
    
    return 0;
}

Next. Add the following lines around the function that spawns the above thread.

/*  Orderly startup. */
//  Initialize graphics handling thread. //
    ui_initlock = SDL_CreateMutex();
    SDL_mutexP(ui_initlock);
    SDL_Thread *graphics_thread = SDL_CreateThread(tUIHandler, NULL);
    if (graphics_thread == NULL)
    {
        cerr << "Unable to create graphics_thread: " << SDL_GetError() << endl;
        return 0;
    }
    SDL_mutexP(ui_initlock); // wait til ui thread has reached condition waiting state.
    SDL_mutexV(ui_initlock);
    SDL_DestroyMutex(ui_initlock);
    ui_initlock = 0;

Finally. To allow the waiting thread to continue execution, you have to send it a condition signal somewhere along the program's execution path. This can happen in another thread of your program.

/*  All preflight stuff checks out. Send condition signal to unblock tUIHandler.  */
	int good = SDL_CondSignal(ui_initcond);
	if (good == 0) printf("STARTUP: condition signal sent.\n");

How it works. Let's say you don't want to spawn any other threads until a particular thread has reached a predicted state.

The program accomplishes this by, first, locking a mutex with a call to SDL_mutexP(). Then the thread creation function runs. The thread creation function returns immediately after it is called. It does not block, it does not wait to see if the thread fared well in its birth, it does not care.

Without the second SDL_mutexP(), the program would immediately continue to the next statements. Instead, it blocks, and waits for the newly created thread to signal that it has reached the SDL_CondWait() statement, wherever you choose to put that within the spawned thread function. By unlocking the mutex, CondWait() allows program execution to continue past the second SDL_mutexP() call. In PortMonitor/G, I block the UI thread from running until the user has selected a network interface to monitor.

SDL_SysWMinfo

Depending on whether you're using X11, Aqua, or Windows, there may be certain tricks you'd like to perform with your applications. The SDL_SysWMinfo interface is a sweet way of accessing window-manager specific information and WM-dependent functions.

One common feature I wanted to implement on Windows was an "always on top" option for PortMonitor/G. The code turns out to be pretty simple:

    #include "SDL_syswm.h"

    ...
    
    #ifdef WIN32
    SDL_SysWMinfo info;
    SDL_VERSION(&info.version);
    if ( SDL_GetWMInfo(&info) > 0 )
    {
    	SetWindowPos(info.window, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE \
                     | SWP_SHOWWINDOW);
    }
    #endif

(c)2004 Max Vilimpoc, research