Mutlithreading Critical Sections

Started by
5 comments, last by Anon Mike 17 years, 7 months ago
I am writing a game server using mutiple threads, and there is quite a bit of shared data between the threads. Do I need to put something in a mutex if it is only reading? I know you need to put it in a mutex if it writes, but I keep going back and forth if you need to or not for reading. Example, do I need to do this: for (int i = 0; i < p->nNumEnemies; i++) { //do whatever } or WaitForSingleObject(p->hEnemiesMutex,INFINITE); int CopiedVariable = p->nNumEnemies; ReleseMutex(p->hEnemiesMutex); for (int i = 0; i < CopiedVariable; i++) { //do whatever } If the second of the two is neccessary, then I have a lot of work to do, there are many instances of that. Thank you Dev578
Advertisement
The basic data problem in threading is that if you read (or write) data while modifying it, everything goes to hell. If you can guarantee that there are no modifications to the target data, you can read it without creating a critical section for the duration of the read.

But the moment you need to start writing, things get rather more complex, and you want to be very careful how you go about it. Remember, mutexes are kernel objects -- you should keep them to a reasonable minimum.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Basically any data that is being accessed by the threads will need protected. You want to ensure that multiple threads can not write to the same piece of memory (as you have already stated). Also you will want to account for a thread writing, while another thread tries to read from the same memory (And vise versa).

For instance each thread is allocated a time slice to run on the CPU. Thread 1 might start reading the data and then its time slice expires. This allows thread 2 to write to the same memory Thread 1 was reading. Thread 2's time slice expires allowing Thread 1 to resume reading the data. Because we did not lock the memory, Thread 1 read in a unexpected corupted value.

Generally if the data is an constant value that will NEVER change, then it might be safe to allow multiple threads to access it without locking. However, I generally try to avoid this to be on the safe side.
I can't imagine that your program is only reading the data.
Somewhere, someone, at some point, is actually writing it, ain't it so?

If it is, then yes, you should keep this mutex, to protect against read-write conflict.
If not, remove it, as concurrent reading is perfectly safe.


Another aspect, is that you might want to protect larger parts of code.
I see that you only care about nNumEnemies variable, and after that you start iterating through the container. The thing is, if someone is modifying the variable after you read it, he is also probably modifying the container itself (for example, removing an element) - while you're iterating! So the entire operation should be protected by one mutex.

Also, you might consider using CRITICAL_SECTION, as it's much more lightweight than mutex, and should be preferred in inter-thread usage, as opposed to inter-process synchronisation, which requirers usage of mutex.

HTH
~def
The read/write situation is a common one in making robust multithreaded apps. What you need is a special kind of mutex which can be locked in a shared or unshared mode. If the lock is grabbed in unshared mode, everyone else blocks on it until the lock is released. If the lock is grabbed in shared mode, only threads which want to grab it in unshared mode block. (To prevent starvation, a thread which is blocking on grabbing in unshared mode will generally cause other threads which want to grab in shared mode to block too). These slides seem to present things pretty well.
yeah, you can run into problems just reading... one scenario would be what if there's nothing to read or rather what if the buffer going to be read isn't ready to be read? in these cases you have to wait for something to read, which would require blocking to prevent reading invalid, corrupt, or possibly old and incorrect data. this is the producer-consumer problem, where the consumer reads what is produced... but the consumer can't read if the producer hasn't written anything yet... hope that makes sense lol.
The first level answer is that if you have to ask, you should use sychronization even for reads. As somebody else mentioned, in Win32 a critical section will be much more efficient than a mutex. This is usually the safest path but if you're not careful you can actually end up with worse performance that a single threaded design and you need to worry about deadlocks.

The next level answer would be that you are safe as long as the reads are atomic. i.e. it's impossible for the data to change while you are reading it. An example would be if you had to read two ints - you might end up with the first int and the second being inconsistent because another thread was writing at the same time you were reading. You can also run into the problem of the data going away entirely while you're reading it.

The third level answer is that even if you get all the above stuff right then on some multiprocessor architectures you can still be screwed unless you use aquire/release. Things get really subtle at this point.
-Mike

This topic is closed to new replies.

Advertisement