Using POSIX Threads

There are two ways to use threads to offload processing from your game loop. The first method is to write a thread function that runs once and then returns. The second method is to write a thread function with its own while loop that runs continually in parallel with your game loop. There are advantages and drawbacks to both methods. The single-run function method uses more processor cycles because the thread function is being called many times per second, but it will result in fewer mutex waits (which happen when the thread is locked by another process). The continually running function with its own while loop is more efficient because it is only called once in order to run in parallel, but the drawbacks are less versatility and more instances of mutex waits. Neither method is better than the other, as both types of thread function will be useful in a game. I tend to favor the single-run thread functions over the embedded loop function method, if only because it allows for smaller, more mission-specific functions. There’s no reason why you cannot write many single-purpose thread functions that run once depending on the conditions in the game.

Let me give you some examples to help you visualize both scenarios. First, you have a game that creates a thread before launching its own main loop. Inside the thread function you have programmed it to update all of the sprites in the entity manager. Since the entities are created on the heap with new, each entity in the list is really just a pointer. Thus, iterating through the entity list means we go through a list of pointers to gain access to each mesh or sprite object in the list. The thread function runs in a tight loop with no timing whatsoever, so it runs really fast. In your game loop, however, each time an entity is updated, there is a call to the game_entityUpdate function in your game’s source code, and a pointer to the entity is passed to this function each time. Now, if your tightly written thread loop tried to read data in a specific sprite while the game_entityUpdate function was writing data to the same sprite, that would crash the whole program—or at best, lock up the thread due to the mutex, which would cause the game to freeze as if it were paused.

Let’s take a look at this scenario from the single-run thread function point of view. In the engine, the entity list is iterated and a thread function is called to update each entity (with movement, animation, collision detection, and so forth). But now, most of that processing is being called from the game loop, not from the thread loop. Whenever we need to update a sprite, a thread function is called, and when that update is finished, the thread is terminated. Our game experiences quite a bit of overhead with all of the function calls, but the advantage is that now we can update an entity in game_entityUpdate or game_entityCollision without causing a mutex lock. How? If, inside one of the single-run thread functions, we experience a thread lock, that thread will wait until the lock is released, and it will then finish its processing and kill the thread. However, in a tight thread loop, the mutex in the main program could be locked instead! This could potentially lock up the game loop. Although the engine’s threads would continue to run just fine (hogging the system, so to speak), the game loop that communicates by way of our event functions (game_keyPress and so forth) would be interrupted. We can predict this because the game loop has timing code in it. That timing code means the game loop can be easily interrupted. The thread loop will have no such timing code, because it is designed to run as fast as possible.

As you can imagine, a lot of thought must be put into a multi-threaded game engine before we just haphazardly create a tight thread function at engine startup and then assume that, with our newfound threading power, the engine will run faster. In reality, the thread locks are probably slowing it down!

Returning to the subject of single-run thread functions, there is another drawback that I have not mentioned yet. When you create a new thread, the point in the program where the thread was created continues to the next statement. The program doesn’t need to wait until the thread function returns before it executes the next line. This means we can actually create many threads simultaneously, with each one updating a single object in memory without conflict. This is inevitably faster than a monolithic thread function if we have a multi-core processor, since a looping thread will only utilize a single core. Our multiple-thread function call theory would utilize multiple cores, since the operating system decides where threads are run and balances them among all available processor cores.

The drawbacks seem to outweigh the advantages to the single-run threads. Since the threads are being created and destroyed thousands of times per second, the overhead will be high, outweighing any advantages we would otherwise gain by supporting any number of processor cores. In addition, creating and destroying threads repeatedly can cause some instability in the framerate of the game loop, making it difficult to maintain a smooth and reliable core. Due to these issues, we will use a single thread function—but we can create more than one if we need to.

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

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