Multi-threaded Programming

Check out the Threading Examples.

SDL provides functions for creating threads, mutexes, semphores and condition variables.

In general, when writing multi-threaded programs. Some good guidelines include:

Note: SDL's threading is not implemented on MacOS Classic, due to the lack of preemptive thread support (Mac OS X does not suffer from this problem).

What are threads?

A normal program without threads executes everything one after the other in sequence, which can make things difficult, say if you want to update your game world at a certain rate but you want to repaint the screen at a different rate. If you put these two things into separate threads, they can run at the same time, or in parallel. Threads are similar to the processes that run on your computer in parallel (if you have a few programs open at the moment, they are each running as separate processes in parallel). The main difference is that threads run inside a process and share memory, whereas processes all have separate memory. This means that you have to be careful with accessing memory when using multiple threads, but on the upside, threads hardly use any extra memory.

Threading is a must on today's hardware for demanding applications. Today companies and programmers are neglecting threads as a means of actually using multi core chips properly. Without threading your application or game will at most use 50% of you computer CPU. Learning how to properly thread an application is very important on today's hardware. A few years back threading was only used for none blocking IO and CPUs were able to optimise application on the fly to run instructions in parallel. Since the early 1990, Pentium Pro and beyond, all CPUs have executed instructions in limit parallel fashion, something previously invisible for the code. CPU manufacturers of today have found it impossible to introduce more logic in the hardware to run more instructions parallel. Last hope was a CPU architecture idea called "Very Long Instruction Computing". This means that CPU manufacturers are exposing more of how CPUs actually process assembler instructions.

Low level threading as opposed to using multiple processing as a very basic application for the common loop. Say that you have

void zeroblock(int* st) {
  for(int i=0;i<901289;i++) {
       st[i] = 0
  }
}

What we want to write in C/C++ but cannot, is the following:

void zeroblock(int* st) {
  for_inanyorder(int i=0;i<901289;i++) { // a hypothetical extension to C where the array would be set in any order by any number of threads
       st[i] = 0
  }
}

In reality we need to use something a lot more complicated, see C++ example below.

Key features to be successful using threads are

The Dangers of multi-threading

There are many articles on the dangers of using multiple threads. It doesn't mean threads are dangerous, it just means you have to be careful with what you do. Essentially, the problem is that if you have a thread doing some work on a variable - maybe adding a number to it or changing it somehow - there is nothing stopping another thread from also changing the variable while the first one's in the middle of something, and then the first thread could produce unexpected results, possibly making your program go haywire and run into the hills screaming. The solution to this is either to not have threads changing common variables, or use one of the various mechanisms to ensure that only one (or however many) threads can access the data at any one time. Common mechanisms are Mutual Exclusion and Semaphores, which are provided as SDL methods (a mutex is used for mutual exclusion). You can of course implement these mechanisms yourself, if you want to. There are books on the subject and they have nothing to do with SDL, and nor does this page really; maybe it should stop here before the SDL Guide becomes a game programming guide. On to SDL!

Using SDL functions in threads

The main advice is not to call SDL video or event functions from separate threads. This doesn't mean you can't call them from a thread, it means you shouldn't have two threads both calling video functions or handling events. The reason is that the SDL functions aren't designed for threads, so they don't know how to handle what happens if two video functions occured at the same time. What would happen if two threads tried to do SDL_BlitSurface()? We just don't know, but it will probably make your program break, and even if it did work it wouldn't be any more efficient, it would probably be even slower.

Example implementation of threads for the trivial case

To actually do threading in the trivial case on a machine with two cores you would do something as seen in the code below. This might not be optimal but even minimal use of threading can speed up an application greatly.

// container of some data

class root_t {
 int* data;
}

// class to hide threading details

class spinlock {
private:
        static int spinLock[2];
        static void* thread(void*);
public:
        spinlock();
        void wait(int off,int cc);
        void start(root_t&);
        virtual void job(root_t&,int jmp,int off);
};




int spinlock::spinLock[16];
struct thst {spinlock* elem; root_t & root;int jmp;int off;int cc;};



void spinlock::wait(int off,int cc) {

        spinlock::spinLock[off]++;
        int sig = spinlock::spinLock[off];
        int wait=1;

        while(wait) {
                wait = 0;
                for(int i=0;i<cc;i++) {
                        if(spinlock::spinLock[i]!=sig) {
                                wait=1;
                        }
                }
        }

}

void spinlock::job(root_t&root,int jmp,int off) {
        throw "no job definition";
}

spinlock::spinlock() {
        for(int i=0;i<2;i++) {
                spinlock::spinLock[i] = 0;
        }
}


void* spinlock::thread(void* thread) {
        thst* arg =(thst*) thread;
        arg->elem->job(arg->root,arg->jmp,arg->off);
        arg->elem->wait(arg->off,arg->off);
        return 0;
}

//
// Create a thread group, (dies after execution)
//

void spinlock::start(root_t& root) {
        static pthread_t ch=0;
        thst a={this,root,2,0,2};
        thst b={this,root,2,1,2};
        pthread_create(&ch,0,spinlock::thread,&a);
        spinlock::thread(&b);
}


// the only thing we need to define

class fill_t : public cmd_t {
public:

        virtual void job(root_t& root,int jmp,int off) {
             for(int i=off;i<901289;i+=jmp) {
                  st[i] = 0
             }
        }

}


int main(int argc, char *argv[]) {
  fill_t fill;

  fill.start(); // start and stops a thread

  return 0;
}

Note: This example has not been tested, anyone up for that?

Note: The example is neither very clear, nor functional, nor self contained. Needs fixing/commenting or removal to avoid confusion.

Multi-threaded_Programming (last edited 2009-08-12 11:49:49 by p4FD894AE)