Programming POSIX Threads

I am going to cover the key functions in this section and let you pursue the full extent of multi-threaded programming on your own using the references I have suggested.

Creating a New Thread

First of all, how do you create a new thread? New threads are created with the pthread_create function.

int pthread_create (
    pthread_t *tid,
    const pthread_attr_t *attr,
    void *(*start) (void *),
    void *arg);

Yeah! That’s what I thought at first, but it’s not a problem. Here, let me explain. The first parameter is a pthread_t struct variable. This struct is large and complex, and you really don’t need to know about the internals to use it. (“Ignorance is bliss,” to quote Cipher from The Matrix.) If you want more details, I encourage you to pick up Butenhof’s book Programming with POSIX Threads (Addison-Wesley Professional, 1997) as a reference.

The second parameter is a pthread_attr_t struct variable that usually contains attributes for the new thread. This is usually not used, so you can pass null to it.

The third parameter is a pointer to the thread function used by this thread for processing. This function should contain its own loop, but should have exit logic for the loop when it’s time to kill the thread. (I use a done variable.)

The fourth parameter is a pointer to a numeric value for this thread to uniquely identify it. You can just create an int variable and set it to a value before passing it to pthread_create.

Here’s an example of how to create a new thread:

int id;
pthread_t pthread0;
int threadid0 = 0;
id = pthread_create(&pthread0, NULL, thread0, (void*)&threadid0);

So you’ve created this thread, but what about the callback function? Oh, right. Here’s an example:

void* thread0(void* data)
{
    int my_thread_id = *((int*)data);
    while(!done)
    {
        //do something!
    }
    pthread_exit(NULL);
    return NULL;
}

Killing a Thread

This brings us to the pthread_exit function, which terminates the thread. Normally you’ll want to call this function at the end of the function, after the loop has exited. Here’s the definition for the function:

void pthread_exit (void *value_ptr);

You can get away with just passing null to this function because value_ptr is an advanced topic for gaining more control over the thread.

Mutexes: Protecting Data from Threads

At this point you can write a multi-threaded program with only the pthread_ create and pthread_exit functions, knowing how to create the callback function and use it. That is enough if you only want to create a single thread to run inside the process with your program’s main thread. But more often than not, you will want to use two or more threads in a game to delegate some of the workload. Therefore, it’s a good idea to use a mutex for all your threads. Recall the ice cream cone analogy. Are you sure that new thread won’t interfere with any globals? Have you considered timing? What if you are using a thread for rendering while another thread is writing to the back buffer? Most memory chips cannot read and write data at the same time. It is very likely is that you’ll update a small portion of the buffer (by drawing a sprite, for instance) while the buffer is being blitted to the screen. The result is some unwanted flicker—yes, even when using a double buffer. What you have here is a situation that is similar to a vertical refresh conflict, only it is occurring in memory rather than directly on the screen. What I am trying to point out is that threads can step on each other’s toes, so to speak, if you aren’t careful to use a mutex.

A mutex is a block used in a thread function to prevent other threads from running until that block is released. Assuming, of course, that all threads use the same mutex, it is possible to use more than one mutex in your program. The easiest way is to create a single mutex, and then block the mutex at the start of each thread’s loop, unblocking at the end of the loop. Creating a mutex doesn’t require a function; rather, it requires a struct variable. In our simplistic approach here, I’m using only a single mutex for the entire program, but in practice you would want to use many mutexes.

//create a new thread mutex to protect variables
pthread_mutex_t threadsafe = PTHREAD_MUTEX_INITIALIZER;

This line of code will create a new mutex called threadsafe that, when used by all the thread functions, will prevent data read/write conflicts. You must destroy the mutex before your program ends; you can do so using the pthread_mutex_ destroy function.

int pthread_mutex_destroy (pthread_mutex_t *mutex);

Here is an example of how it would be used:

pthread_mutex_destroy(&threadsafe);

Next, you need to know how to lock and unlock a mutex inside a thread function. The pthread_mutex_lock function is used to lock a mutex.

int pthread_mutex_lock (pthread_mutex_t * mutex);

This has the effect of preventing any other threads from locking the same mutex, so any variables or functions you use or call (respectively) while the mutex is locked will be safe from manipulation by any other threads. Basically, when a thread encounters a locked mutex, it waits until the mutex is available before proceeding. (It uses no processor time; it simply waits.)

Here is the unlock function:

int pthread_mutex_unlock (pthread_mutex_t * mutex);

The two functions just shown will normally return zero if the lock or unlock succeeded immediately; otherwise, a non-zero value will be returned to indicate that the thread is waiting for the mutex. This should not happen for unlocking, only for locking. If you have a problem with pthread_mutex_unlock returning non-zero, it means the mutex was locked while that thread was supposedly in control over the mutex—a bad situation that should never happen. But when it comes to game programming, bad things do often happen while you are developing a new game, so it’s helpful to print an error message for any non-zero return.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset