Game related Multi-threading

Started by
5 comments, last by ddboarm 14 years, 3 months ago
Just wondering if anyone had any good links to material on multi-threading, preferbly related some how to games in C++ using win32. Whether it be some book or something online. Ive tried reading some books on the subject, but a lot of what I have read was mainly about theory, and I became lot fairly quickly without sample code, as I seem to learn best from example code. At the moment, my understanding of it is simply a case of finding code that can be run in parallel, creating a number of threads, having those threads perform different tasks, and then wait for them all to finish before continuing. Something like :-


// Need to calculate matrix palette for 3 characters

Thread m_Threads[3];
CreateThreads(Threads);

RunThreadWithFunction(CalculateMatrixPalette(Character1));
RunThreadWithFunction(CalculateMatrixPalette(Character2));
RunThreadWithFunction(CalculateMatrixPalette(Character3));

WaitForMultipleThreads(m_Threads);

// done

CloseThreads(Threads)


But I know its not this simple, and I have questions such as; 1. How do you determine how many threads you should use? 2. I understand that you can't have multiple threads working on the same data, (unless it is read only) as some maybe changing somthing that others are updating, and it will all go horribly wrong due to Syncronization problems. And to solve this you use Mutexs, that essentially mark the data as being used by some thread, where others have to wait for the mutex to unlock before they can then have their turn, and so on. But this sounds a lot like sequential programming to me. If threads are waiting for other threads to finish, wouldn't you get very little performance increase here? (if any)
Advertisement
Have a read at this: Gamasutra: Multithreaded Game Engine Architectures

1) You DON'T want to be creating threads all the time. Create a thread pool of ~N threads where N = # of hardware supported threads. Hardware supported threads is different from cores, things like hyperthreading give each core multiple hardware supported threads. Give all the threads access to a thread safe job queue (there are mutexed versions, and lockless versions to choose from). When the thread is done with its current task, it waits for a new job in the queue that it can work on. Each 'job' is really just a combination of data and function pointers that the thread would call once, and when the function returns it would go to the next item in the queue.

If you have tasks that don't take up a lot of cpu time, they can be in their own threads. Many background tasks do this already, as will different middleware. There can threads for audio, video, networking, file loading, etc.

2) To relieve the serial computation problem, there are several things you can do, but you have to remember that much of a games pipeline is serial by nature. You need to check inputs, update entities, render for the user to see.

a) don't worry about serial bottlenecks, and just thread off things that you know are parallel, and sync at the end of the job set. Animating each skeleton only depends on the skeleton and it's inputs. Your matrix palette example. Any software culling or other data preparation task that only takes input data and builds lists of output data.

b) in places where you have reading and writing to a data set (entity updates), double buffer the data. Have a "last frame" and "next frame" buffer. All reads happen safely from the last frame, all writes happen safely to next frame. The only sync you then have to do is after the entity update jobs are done, sync and swap the buffer pointers.

c) more advanced, double buffer the interactions between systems. The entities update independent of the visual representations, and after the render thread is done rendering a frame, it syncs its internal buffers from the external entity data.

d) Put sync points as far from start points as reasonable. Decompressing data from disk only needs to signal the appropriate location that it is done. You could run a bunch of A* searches in a job that starts at the end of the entity updates and syncs just before the next frame's entity updates.

All and all though, remember that most solutions to the threading synchronization issues will cause latency between the input and render stages.
3-5 frames latency at 30fps is a lot more than at 60fps. So you better be able to target a higher frame rate if you have a lot of buffering going on.
Thanks for your reply. Whilst the article was helpful, it still doesnt really provide any example code. I've been looking through the win32 Thread APi, and there is just a mass of functions. Im really looking for some good example code that builds things up step by step, rather than just throw it all at you at once like the MSDN sample code.
Quote:Original post by maya18222
Thanks for your reply. Whilst the article was helpful, it still doesnt really provide any example code. I've been looking through the win32 Thread APi, and there is just a mass of functions. Im really looking for some good example code that builds things up step by step, rather than just throw it all at you at once like the MSDN sample code.


I wouldn't use the Win32 API directly. At the very least use something like the boost threads library (coming to a C++ standard library near you soon, anyway). Ideally, you should go higher still and look at something with functionality like Intel's TBB, OpenMP, etc. You really want to get to the point where you're thinking mainly in terms of tasks rather than threads.

Actually, Intel's Visual Adrenaline online magazine (free) often contains techniques for writing threaded games and simulation code. It's very Intel-centric of course, but the ideas are applicable with whatever frameworks and libraries you use.

When looking for material, don't restrict yourself to looking for games-centric stuff. Books like this one will give you an idea of how to split work at task or data boundaries in order to add parallelism to an application and also provide an introduction to common patterns in parallel applications.

If you're completely new to using threads, David Butenhof's Programming with POSIX threads provides a good introduction to the nuts and bolts of threading.
Thanks for the reply. I shall take a look at the books you recommended. Do you recommend a book on TBB too? How does TBB differ to Boost.Threads?


Thanks
Quote:Original post by maya18222
Thanks for the reply. I shall take a look at the books you recommended. Do you recommend a book on TBB too? How does TBB differ to Boost.Threads?


Thanks


TBB is good, it's based on modern C++ design and it is used by a number of games companies (including where I work). Very much like the standard library generic container & algorithms. The difference with Boost.Thread is boost.thread is low-level API (does not mean it is bad).

TBB provides concurrent containers, generic algorithms and these are built on top a of a task scheduler based on one of the best algorithms for load balancing, the work stealing algorithm. You do not need to be concerned with things like how much work to distribute over each core/processor you have, TBB deals with that. You would have to implement these things yourself with boost.thread and you don't get concurrent containers, boost currently lacks a thread pool (a officially).

There is an official book for TBB.

One downside of TBB is the lack of lambda expressions currently until C++0x is finalized and published next year (hopefully). This is not the fault of TBB of course.

[Edited by - snk_kid on December 27, 2009 11:07:38 AM]
A previous post is correct in that you don't want to create threads left and right. Any many scenarios where I have thought I needed a thread, I have been able to replace with asynchronous method implementation (Delegates, BeginInvoke, etc.). Just thought that I would throw that idea out there for possible consideration.

This topic is closed to new replies.

Advertisement