I'm also not looking for the "must be absolutely thread safe" |
Code is either thread-safe or it isn't. If it's not, your program will be unstable and buggy, and it will be very hard to fix the bugs.
If you don't design your program with thread safety from the start, it's very difficult to work it in later.
"Lock-free" code is very likely to not work. Even code that people have written and posted on the internet is known to be broken when run on multicore machines. Synchronizing two or more threads
absolutely must have a memory barrier around key variable accesses. There is no getting around that.
Any tutorial that tells you to use "volatile" to make your variables threadsafe is wrong and should not be trusted. Volatility has nothing to do with threadsafety in C++. (The exception is that
some versions of MSVS [but not all] put memory barriers around volatile variable accesses, making them threadsafe, but other compilers don't do that, so you shouldn't rely on it).
There are a few ways to create a memory barrier. The most common is with a mutex. Another option is built into C++11's atomic class.
Example of bad code that will not work:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
int data = 0;
bool dataready = false;
void altthread()
{
// provide some data
data = 5;
// tell the main thread that the data is ready
dataready = true;
}
void mainthread()
{
while(!dataready); // wait for data to be ready
cout << data; // BAD, 'data' may not actually be written yet.
// might output 0, might output 5, might output something else entirely.
}
|
The question you really have to ask yourself is "Would making this game multithreaded really be an improvement?". It probably wouldn't be. Games tend to be linear in design, so one thread is usually all you need for the logic.
Adding more threads may not even give you a speed boost. I recently did an experiment where I tried to add multithreading to an emulator I was writing. Aside from having to spend days debugging to stop it from crashing at random times (issues with thread safety), it actually HURT performance a lot.
The numbers are here:
http://forums.nesdev.com/viewtopic.php?p=96069#p96069
("preemptive" = multithreaded as you know it
"cooperative" = using one thread to simulate many threads. For purposes of this illustraction, you can consider cooperative as being not multithreaded)
Now granted... emulators will have to "sync up" their threads more often than a normal game would. But As the numbers show I was only doing about 11 syncs per frame... and even with that low of a sync count, there was a performance decrease of ~34 FPS.
So unless you're just looking to experiment and get your feet wet with multithreading, I wouldn't bother.
EDIT:
That said... as for your actual problem... you could do this with a std::list and std::function (C++11) pairing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
#include <functional>
#include <list>
typedef std::list< std::function<void()> > calllist_t;
calllist_t calllist;
void func1(int a); // callback that takes 1 param
void func2(int a, int b); // callback that takes 2 params
void producer_thread()
{
// NOTE: accesses to 'calllist' MUST be behind a mutex (not shown here)
// we want the main thread to call func1(10);
calllist.push_back( std::bind( func1, 10 ) );
// then have it call func2( 3, 4 );
calllist.push_back( std::bind( func2, 3, 4 ) );
}
// main thread
void consumer_thread()
{
// NOTE: accesses to 'calllist' MUST be behind a mutex (not shown here)
while(!calllist.empty())
{
// call the function at the head of the list
std::function<void()> func = calllist.front();
calllist.pop_front();
func(); // call the function
}
}
|