If I understand meaning of volatile and MemoryBarrier correctly than the program below has never to be able to show any result.
It catches reordering of write operations
You aren't cleaning the variables between tests, so (for all but the first) initially a
is 2
and b
is 20
- before Write
has done anything.
Check
can get that initial value of a
(so tempA
is 2
), and then Write
can get in, get as far as changing b
to 10
.
Now Check
reads the b
(so tempB
is 10
).
Et voila. No re-order necessary to repro.
Reset a
and b
to 0
between runs and I expect it will go away.
edit: confirmed; "as is" I get the issue almost immediately (<2000 iterations); but by adding:
while (continueTrying)
{
a = b = 0; // reset <======= added this
it then loops for any amount of time without any issue.
Or as a flow:
Write A= B= Check
(except first run) 2 20
int tempA = a;
a = 1; 1 20
Thread.MemoryBarrier();
b = 10; 1 10
int tempB = b;
If you use MemoryBarrier
in writer
, why don't you do that in checker
? Put Thread.MemoryBarrier();
before int tempA = a;
.
Calling Thread.MemoryBarrier();
so many times blocks all of the advantages of the method. Call it only once before or after a = 1;
.
I don't think this is re-ordering.
This piece of code is simply not thread-safe:
while (continueChecking)
{
int tempA = a;
int tempB = b;
...
I think this scenario is possible:
int tempA = a;
executes with the values of the last loop (a == 2)b = 10
and the loop stopsint tempB = b;
executes with b == 10I notice that the calls to MemoryBarrier() enhance the chances of this scenario. Probably because they cause more context-switching.
The result has nothing to do with reordering, with memory barries, or with volatile. All these constructs are needed to avoid effects of compiler or CPU reordering of the instructions.
But this program would produce the same result even assuming fully consistent single-CPU memory model and no compiler optimization.
First of all, notice that there will be multiple Write()
tasks started in parallel. They are running sequentially due to lock() inside Write(), but a signle Check()
method can read a
and b
produced by different instances of Write()
tasks.
Because Check()
function has no synchronization with Write
function - it can read a
and b
at two arbitrary and different moments. There is nothing in your code that prevents Check()
from reading a
produced by previous Write()
at one moment and then reading b
produced by following Write()
at another moment. First of all you need synchronization (lock) in Check()
and then you might (but probably not in this case) need memory barriers and volatile to fight with memory model problems.
This is all you need:
int tempA, tempB;
lock (locker)
{
tempA = a;
tempB = b;
}