I have a good understanding of the system objects available to protect the counter variable above. This example comes from a larger system. Adding one line of code, would take months to rebuild, retest, redeploy.
In a finite system such as computer programming, I am trying not to assume anything. I am trying to figure out if the above assuptions are the only possible influences on the counter variable (comment about printf statement noted).
A colleague insists that at one time or another the variable could contain a "garbage" value. I tend to insist that the value x will always clearly reflect the number of operations performed - (minus) the number of times the race condition is hit.
Anybody willing to take sides?
Last edited by RwAA23; March 30th, 2009 at 04:39 AM.
>> Anybody willing to take sides?
The actual answer depends on the CPU architecture and the assembly instructions generated by the compiler (which could easily change with the change of a compiler option...).
The general answer has been given. Anything can happen - meaning any value read from x can be considered "garbage".
Ok, thanks again for the posts. I get the feeling that I'm going wrong somewhere and if anybody has the patience to go back to basics to find out where, please let me know.
I start with x=x+1 which (to me) means read x, add 1 to it, write x.
For me, adding concurrency means that if a thread is interrupted between the read and the write, then both threads "think" they've incremented x; so you can never be sure that x is reflecting a correct value for whatever this program is doing; thus the need to protect access to it.
What I am getting back on these posts is that there is a possibility to interrupt threads during the read and write operations, does this mean the value of x can be partially written while being read? That's what I am having a difficult time with.
Or maybe another possibility, could some OS architectures require more than one operation to read or write; meaning that they can be interrupted between operations rather than during?
>> does this mean the value of x can be partially written while being read?
>> require more than one operation to read or write; meaning that they can be interrupted between operations
There are many ways un-synchronized access to shared to shared memory can break your code:
* re-ordering of program-order memory accesses by the compiler
* re-ordering of inter-thread memory accesses by CPU cache-coherency and speculative execution algorithms of the HW
* all the various interleavings of threads executing non-atomic instructions
If you want to tell us the CPU, OS, and compiler you're using, then one could speculate as to what could actually occur when multiple threads execute "++x". In general it's a LOAD, INC, STORE - each of which may or may not be atomic.
Last edited by Codeplug; March 31st, 2009 at 02:52 PM.
For the value of x, I think I can put it to rest. Without synchro, the value can be anything at any given time.
Just one last question, could you briefly explain or give an example to what you eluded to in ticket 6? IS this relating to a "bad" value of x being passed further down the line and having a bug appear elsewhere? Or, is there some other black voodoo magic dangerous to the real world ;-) about having two thread both read and write to the same static memory location?
Or, is there some other black voodoo magic dangerous to the real world ;-) about having two thread both read and write to the same static memory location?
It's totally black voodoo magic. Either that or experience.
In simple MT programs, race conditions can be tracked down relatively easily. An experience [mt] developer can usually spot the problems with a quick code inspection and may even be able to predict the type of failure(s).
However, when the complexity of the system goes up, it becomes more difficult to understand or predict how or in what module the program will fail.
Depending on the programmers style, it may be difficult to isolate these issues. For example consider a style where global variables are shared between threads (not an approach I recommend) in a complex program. If any unprotected access is made to the shared variable you will have unpredictable results which may manifest themselves in behaviors that aren't at all expected. Sure, when you track the issue down you can usually explain the behavior such that this unprotected global had a race condition that led to this, then this, and finally this. However, not always can the resultant behavior in complex systems be explained by immediately understanding that it's caused by an unprotected variable.
As I said, I wouldn't recommend sharing a global variable between threads. I prefer to provide some isolation and encapsulation with data shared between threads. For example, I would create a class containing the data and create some synchronized accessors that read and write the data. The different threads that consume the class, don't need to worry about the synchronization chores - they simply call the accessors (and the synchronization happens within the class).
Another synchronization approach that I follow is to use the RAII (Resource Aquisition Is Initialization). I use a couple of synch wrapper classes that wrap a critical section, mutex, and reader writer lock implementation and then a 'locker' class that provides the RAII support. The locker class provides the RAII by simply obtaining a lock in the ctor and automatically releasing it in the dtor. This ensures that a lock is always released and greatly simplifies the code.
I mentioned earlier that I don't sweat knowing the atomic operations specific to a platform. The code I wrote isn't usually platform specific so I don't what to get into maintaining special cases as to whether I require synchronization on this platform or that. My general rule is that if I am sharing a variable across threads, then I synchronize access. The exceptioin to this is when the data in the variable is created prior to being accessed by the multiple threads and as a result is only ever read-only. Of course, part of getting good performance with regard to multithreading is to wisely choose the proper synch primitives (cs, mutex, etc.).
>>Am I right to expect that both threads may (from time to time) print the >>message for the same 100 operations?
Theoretically its possible that it may not print anything at all..
Consider this scenario.
when x was just evaluating 99. Thread one kicks in and makes it 100, thats allright. But before it could do x%100, thread 2 kicks in and increase it to 101. Now ++x%100 will evaluate 1 and hence would not print.
SO theoretically it may happen that it never print.
Dont forget to rate my post if you find it useful.
Thanks to everyone, I really enjoyed this discussion and I fully agree that failure to protect shared variables is normally bad form, sloppy and / or lazy. That can lead to "dangerous" situations since garbage values are not always expected nor are they handled safely. It is far better to avoid the garbage values rather than let them happen and have to defend against them later.
I'll come clean. I started thinking about the consequences of this race condition as I was writting a quick program to diagnose something. The program had something like heartbeats, with a tolorance to miss one (or two in a row (which would have happened if the race condition was hit)). I knew it was a patch job, and couldn't be bothered adding sychronisation. Even adding one line of code with a "auto" critical section (similar to what Arjay explained with a lock & unlock in the ctor & dtor) add a dependancy to another class or library.
Thanks again for the discussion, I wouldn't mind having you all over to my place sometime. I run a pretty tight ship. Heck you can almost eat off the floor. But I'll tell ya a little secret, I once left meatloaf in my fridge for over a month ;-)
It should be pointed out that not all race conditions are capable of generating garbage, and not all race conditions are---strictly speaking---bad. But they're important to be aware of.
Let's say that Event A and Event B occur in different threads. They access some of the same information, but are not strictly reliant on one or the other happening first. So long as that information is properly protected by a critical section, there's no danger of garbage being introduced. However, a race condition still exists; and if at some future point the ordering of A and B becomes relevant, this would need to be addressed.