Sign in to follow this  
Decrius

[C++] Threads

Recommended Posts

So, I have to start using threads now and I have a few questions about them. Would I be better using SDL_threads or pthreads (or any other)? What are their differences? Mutexes: In order that threads will not access the same memory piece, I will have to use mutexes...but, what if one thread reads and writes a certain memory block and the other thread will ONLY read it...would I need to use mutexes? Are mutexes only needed with writing to memory? Are mutexes CPU consuming? I mean, if I'd lock and unlock every frame, would it slow the application down a lot? Thanks, Decrius

Share this post


Link to post
Share on other sites
I recommend the boost::threads library. It's a far superior alternative to any platform-specific library, IMO.

If a shared resource has a chance to be written to, then all threads accessing it need a mutex. Otherwise, what happens if your second thread reads your data while your first thread is writing to it? You don't need to use a mutex if all threads only ever read from the data.

Synchronisation primitives aren't cheap. But if you find that synchronisation primitives are sucking up performance, then you're synchronising data way too much and you'll need to rethink your design.

A note: multithreaded game design is a big topic, and it's notoriously hard to do. Multithreading in general is difficult; adapting it to something as complex as a game is extra hard. Think long and hard about whether you really need to multithread your game, and whether it will be worth the trouble and risk.

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
So, I have to start using threads now and I have a few questions about them.

Would I be better using SDL_threads or pthreads (or any other)? What are their differences?


You're better of using a higher level of abstraction. If you really need manual control over threads, then I second Sc4Freak's suggestion of Boost.Threads.

Look at things like TBB, OpenMP and libraries for thread pools and task pools.

Quote:

Mutexes:
In order that threads will not access the same memory piece, I will have to use mutexes...but, what if one thread reads and writes a certain memory block and the other thread will ONLY read it...would I need to use mutexes?

Yes.

Quote:
Are mutexes only needed with writing to memory?

You need a mutex whenever two or more threads read and write to an associated memory location containing mutable data.

Quote:
Are mutexes CPU consuming? I mean, if I'd lock and unlock every frame, would it slow the application down a lot?

Perhaps if you used a large number of mutexes, yes. But don't be too afraid of locking. Finding "shortcuts" often leads to buggy code unless you *really* know what you're doing.

Make sure that you hold locks for the shortest amount of time possible. This might mean that you take a lock, copy the shared data, unlock and then work on the copy. It's kind of counter-intuitive that this kind of thing can often be faster than doing the work in situ while holding a lock.

Also, it's much harder to think in terms of a fixed number of threads (greater than 2, anyway). Where possible, design for N threads.

Share this post


Link to post
Share on other sites
Terminology:
Mutex - Mutual Exclusion is a "marker" that you allocate (1 per resource you wish to protect). It's passive and does nothing.

Lock - Each time you access a resource you obtain a lock using a specific mutex. If another thread is locking on same mutex already, your thread will need to wait until it's released.

Quote:
Original post by Decrius
So, I have to start using threads now and I have a few questions about them.

Would I be better using SDL_threads or pthreads (or any other)? What are their differences?


Your choice. pthreads is generic portable threading library. SDL threads are SDL specific, perhaps simpler to use.

Quote:
Mutexes:
In order that threads will not access the same memory piece, I will have to use mutexes...but, what if one thread reads and writes a certain memory block and the other thread will ONLY read it...would I need to use mutexes?


Yes. There are methods to achieve that. But in general, for start, assume that locks are what you'll use.

Quote:
Are mutexes only needed with writing to memory?


You use locks when you wish to protect integrity of a certain resource. Usually, you need them on every access. Let's say we have a coordinate: (x=10, y=20). If you only lock while writing, the following can happen:
// Assume that we set the coordinate to (-1,-1)

- Thread A sets x = -1
// thread B is activated
- Thread B reads x as -1
- Thread B reads y as 20
// thread A is activated
- Thread A sets y as -1

Apparently, the coordinate is (-1,20), which is incorrect. So yes, you need to lock all accesses to resource.

Quote:
Are mutexes CPU consuming? I mean, if I'd lock and unlock every frame, would it slow the application down a lot?


Mutexes are a resource that you allocate. A handle, they don't do anything.

When you lock on a mutex however, you may block other threads that wish to do the same. Do this improperly, and your 50 threads will all need to wait for 1 single resource, resulting in single-threaded performance.


Warning: threads and locks are one of poorest, yet currently only general purpose concurrency method. They aren't easy, there's millions of pitfalls, and even more problems. So don't go crazy with them.

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
In order that threads will not access the same memory piece, I will have to use mutexes...but, what if one thread reads and writes a certain memory block and the other thread will ONLY read it...would I need to use mutexes?
No, but mutexes are a relatively safe way to do it. In many cases, you can achieve the same using lock-free approaches. However, while these are significantly faster, they too are a lot harder to implement and get right.
Mutexes work relatively painlessly, and while it is still possible to run into problems with them, they are by far the preferrable solution for beginning with threads.

Quote:
Are mutexes CPU consuming?
Very, but there is no choice, you need them.

Share this post


Link to post
Share on other sites
Thanks for the long replies :)

I was thinking of 2 threads: one for the drawing and one for the networking (and eventually one for the event processing, but I guess thats not needed).

Since the network thread would gather information about positions etc, it would write that to memory (a few times per second I was thinking about). Then the graphics/drawing part would just read as often as possible the positions...so it doesn't really care if the networking thread hasn't yet updated the newest information if the drawing thread requests it...it will get the newest info next frame.

Could this be a problem? That if thread 1 is halfway writing and thread 2 reads it? Or can that not happen?

If it could not happen it could save me using the mutexes and the locking...otherwise I'll have to call them per frame...

Share this post


Link to post
Share on other sites
Quote:
Original post by Decrius
Since the network thread would gather information about positions etc, it would write that to memory (a few times per second I was thinking about). Then the graphics/drawing part would just read as often as possible the positions...so it doesn't really care if the networking thread hasn't yet updated the newest information if the drawing thread requests it...it will get the newest info next frame.

Could this be a problem? That if thread 1 is halfway writing and thread 2 reads it? Or can that not happen?


This absolutely can and will happen. You must use locking or some other method of synchronization (but don't go there yet, stick to mutexes and locks for now).

Again, don't be too afraid of locking. Finding "shortcuts" often leads to buggy code unless you *really* know what you're doing.

Share this post


Link to post
Share on other sites
Alright :), I'll just simply lock and unlock it :). I will also test what the performance hit will be, and see if it's neccessary to optimize it in future.

Thanks!

Share this post


Link to post
Share on other sites
Using threads and locks is probably the worst way of doing multi-threading (this is an almost direct from Dr. Bjarne Stroustrup btw.). Of course there ultimately needs to be threads and it's also quite good to at least know about threads and locks, but using them explicitly is really bad most of the time.
As someone pointed out before, you should really use higher-level abstractions like fork-join frameworks, task pools, futures, etc.

The trouble with locking is that if you don't know what you're doing (and even good programmers generally don't), it's very easy to create sequential bottlenecks. It's not so much that acquiring a lock is slow (acquiring a critical section can be relatively fast), it's how it impacts the flow of your application that's the problem. Often enough, you write an app that is nice and parallel and then there's this big ugly lock that essentially kills all that parallelism in a single line of code.

So yeah, learn about locks and threads because it's good to know this stuff, but also learn about how to eliminate shared state alltogether and learn about message passing which is another good way of dealing with concurrency.

Share this post


Link to post
Share on other sites
Quote:
Original post by the_edd

Again, don't be too afraid of locking. Finding "shortcuts" often leads to buggy code unless you *really* know what you're doing.


Be very afraid of locking.

Deadlocks happen. So do livelocks.

First rule about locks is to always obtain them in same order. If you need to lock on resource A, and within that lock obtain resource B, always do it in same order. If two threads obtain these locks arbitrarily, you're begging for a dead-lock.

If you can, create multiple resources, rather than lock them. A typical example would be resource loading thread and networking thread, both requiring zlib library. Rather than accessing zlib codec state globally (it's 256k per) and lock access to it, create one state per thread and skip the locking.

Always test on single core and multiple core machines. Thread starvation (when one thread is hogging all CPU, leaving others no time to run) happens frequently on single core machine. On multi-core, spin-locks (which do exactly that), are sometimes ideal mechanism.

Never try to fix things by randomly adding Sleep or Yield into logic. Thread's load must be managed deterministically, or even slight change in CPU/memory capacities will completely break things.

Context switches are expensive, so running too many threads will increase overhead. Thread creation is incredibly expensive too. Usually, 2 threads per core are maximum if you're aiming for efficiency.

Share this post


Link to post
Share on other sites
Ah okay, lots of different opinions :P

I was thinking of just the main loop with a second sub-loop for the network.

Since the network loop will run slower, and the main loop should not suffer from that, I thought it would be nice to let them run parallel...I've only heard of threads yet and not of

"higher-level abstractions like fork-join frameworks, task pools, futures, etc."

Thanks for the ideas, I'll dive more into it and do some more investigation :).

Share this post


Link to post
Share on other sites
Quote:
Original post by Antheus
Quote:
Original post by the_edd

Again, don't be too afraid of locking. Finding "shortcuts" often leads to buggy code unless you *really* know what you're doing.


Be very afraid of locking.


I should have qualified my statement: don't be afraid of the *costs* of locking at this stage, where the alternatives are much harder to get right and risks involved in omitting locks aren't worth it.

I didn't mean to suggest locking correctly is easy, but you must do it anyway.

On top of the additional advice you gave, I would encourage the O.P to create code that can be run in a single thread when a particular switch is flipped in the code. This allows the ability to test algorithm logic in isolation (more or less) from synchronization logic.

Share this post


Link to post
Share on other sites
There’s also this technique called “polling” — you can ask a socket if it can be written to/read from — thus allowing you to perform network I/O only when there’s data for you to process. In pseudo-code your main loop can then look like this:
while ( running ) {
if ( networkReady() )
handleNetworkStuff();
handleGameStuff();
}
Of course, I don’t know anything about your design and your particular problem, so this might as well not be an option for you. But approach like this has almost always worked for me — I can get non-blocking socket operations without the hassle of threading.

Share this post


Link to post
Share on other sites
Yes, I might try that first...but I thought the networking part could take up some time...thanks for the idea ^^.

Share this post


Link to post
Share on other sites
Quote:
Original post by Oxyd
There’s also this technique called “polling” — you can ask a socket if it can be written to/read from — thus allowing you to perform network I/O only when there’s data for you to process. In pseudo-code your main loop can then look like this:
while ( running ) {
if ( networkReady() )
handleNetworkStuff();
handleGameStuff();
}
Of course, I don’t know anything about your design and your particular problem, so this might as well not be an option for you. But approach like this has almost always worked for me — I can get non-blocking socket operations without the hassle of threading.

But you need to guarantee that your neworkReady() function is atomic. It's not guaranteed to be so, even if all it does is read a boolean. A thread could be writing to that boolean while you're reading from it, meaning you need a lock anyway.

Share this post


Link to post
Share on other sites
All but the poorest semaphore implementations have a method named something like "TryLock()" where it will not block the calling thread but see if the object is currently lockable, and will do so if it is.

As someone else posted, you could check for this in your game loop. I'd add that you could use the same paradigm for network messages, messages from the OS (i.e. WndProc routine), and anything else involving asynchronous messaging. The messages could all be gathered on seperate threads apart from the main thread (or one seperate thread for all messaging); these seperate threads would "WaitLock()" while the main thread would "TryLock()".

That being said, I also want to echo that game engine design generally does not lend itself well to threading, so you should only do it if you have a very good reason.

Share this post


Link to post
Share on other sites
Quote:
Original post by Sc4Freak
But you need to guarantee that your neworkReady() function is atomic. It's not guaranteed to be so, even if all it does is read a boolean. A thread could be writing to that boolean while you're reading from it, meaning you need a lock anyway.


Um, why would I need that? I was talking about an example of a single-threaded implementation — no “other” thread can write to my variables while I’m reading them — there is no other thread.

Share this post


Link to post
Share on other sites
If you have many readers and few writers, a reader/writer lock might be a good idea.

A RW lock (which are't that easy to write: there are pitfalls) allows any number of reader threads into it at a time. Or one writer thread.

Existing ones usually deal with starvation problems, but if you whip one out yourself you will almost certainly screw up. :)

...

Generally you should avoid actually writing locking code more than a handful of times. Locks are dangerous.

A non-horrible way to avoid writing locking code is to break your code down into jobs: rendering, UI input, world model, etc.

These jobs communicate to each other over message queues, which are carefully written with locks & stuff to deal with multi-threading.

Each job only communicates via messages. There is no shared data that isn't in the messages.

The downside? You have to copy world state a whole bunch.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this