[C++] Threads

Started by
16 comments, last by NotAYakk 16 years ago
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
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
Advertisement
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.
NextWar: The Quest for Earth available now for Windows Phone 7.
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.
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.
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.
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...
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
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.
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!
[size="2"]SignatureShuffle: [size="2"]Random signature images on fora
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.
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.

This topic is closed to new replies.

Advertisement