Reputation: 123612
I have the following code:
public static System.Int32 i = 0;
static void Main(string[] args)
{
new Thread(Worker1) { IsBackground = true }.Start();
new Thread(Worker2) { IsBackground = true }.Start();
Console.WriteLine("Running... Press enter to quit");
Console.ReadLine();
}
static void Worker1(object _)
{
while (true)
{
var oldValue = i;
i++;
var newValue = i;
if (newValue < oldValue)
Console.WriteLine("{2}, i++ went backwards! oldValue={0}, newValue={1}!", oldValue, newValue, DateTime.Now.ToString("HH:MM:ss.fff"));
}
}
static void Worker2(object _)
{
while (true)
{
i++;
}
}
When run, this produces output that looks like this:
17:08:17.020, i++ went backwards! oldValue=6124653, newValue=6113984!
17:08:17.057, i++ went backwards! oldValue=18764535, newValue=18752368!
17:08:17.086, i++ went backwards! oldValue=27236177, newValue=27236176!
17:08:17.087, i++ went backwards! oldValue=27550457, newValue=27535008!
17:08:17.130, i++ went backwards! oldValue=40251349, newValue=40235492!
17:08:17.137, i++ went backwards! oldValue=42339974, newValue=42323786!
17:08:17.142, i++ went backwards! oldValue=43828602, newValue=43828436!
17:08:17.149, i++ went backwards! oldValue=45969702, newValue=45959111!
17:08:17.158, i++ went backwards! oldValue=48705847, newValue=48705549!
17:08:17.230, i++ went backwards! oldValue=71199684, newValue=71199674!
Note: this is being run on a quad-core i7 with hyperthreading on windows 7
As far as I can tell, either:
Thread2 is incrementing i approx 4 billion times (but not quite!) in between the reads of oldValue and newValue. Given the numbers and timings above though, it seems like there is a higher chance of me winning the lottery 10 times in the next 2 seconds.
The CPU and compiler are doing some reordering... This seems the logical explanation, but I can't quite figure out a sequence of operations that could actually cause this?
Can anyone shed some light on it?
To clarify the question: I am deliberately looking for code which reproduces memory reordering bugs as part of an educational excercise. There are many ways to fix this, I was mainly interested in analyzing what was happening.
@Tymek has shown the (blindingly obvious now that it's pointed out) answer below.
Upvotes: 2
Views: 226
Reputation: 34947
You surely know that i++ is not atomic and looks really like something like this:
i
from memory to a register,i
is, i
;This sequence can be interrupted at any stage.
Having this in mind, one case which generates your message of doom is a follows:
i
be 100
;100
and is interrupted;i
as 120
, reads i
and increments it to 121
and is interrupted before var newValue = i;
;i
to 101
and is interrupted;var newValue = i
<- this will read in 101
;Now oldValue
is 120
and newValue
is 101
.
Upvotes: 4
Reputation: 5483
To avoid this issue you could use Interlocked.Increment it will garantee that operation will be atomic and thread safe. When you use i++ it is not atomary and value of it could be changed during incrementation (from another thread e.g.).
Upvotes: 1
Reputation: 9384
If u want that the assignment of the old-value, the incrementation of i and the assignment of the new-value happens "at the same time" u should use a lock. Just introduce a private variable and then use the lock on this. This looks something like:
lock(lockObj)
{
var oldValue = i;
i++;
var newValue = i;
}
Upvotes: 1