Does memory barrier ensure data coherence across threads when there is no locking and no concurrent data access except from the parent thread ?
Here is my scenario :
- the Main thread launch several child threads
- the childs threads wait for the state to be ready
- the Main thread starts working on a global set of data, and notify the childs threads went the sate is ready. Then the Main thread wait for the child to process.
- each child thread works on a different subset of the global set of data (either the objects instance are totally different or the threads work on different final member/part of the object memory) ==> each memory address is read/write by at most ONE child thread
- when a child thread finish is core work, it notify the Main thread. The child thread keeps running on specific data of its own.
- when all childs finish their core work, the Main thread resume its work with the global set of data modified by the child threads
I would like to ensure that :
- i. each child thread sees a fresh/clean state when doing their core work (that is to say that all the writes (from the main thread ) performed before starting the child thread are seen by the child thread)
- ii. upon resuming, the Main thread sees a fresh/clean state (that is to say that all the writes (from the child threads) performed by the child thread during their core work are seen by the Main thread)
Here how the code is:
public class State
{
public string Data1 { get; set; }
public string Data2 { get; set; }
public int[] SomeArray { get; set; }
}
static void Main(string[] args)
{
var finishedCounter = 0L;
var ready = 0L;
var state = new State();
var thread1 = new Thread(o =>
{
while (Interlocked.Read(ref ready) != 1)
{
//waiting for initialization
}
var input = (State)o;
//Assert.AreEqual("Initial Data1 From MainThread", input.Data1);
Thread.MemoryBarrier();//TMB 1.1 Force read value pushed from mainThread
//Core work
Assert.AreEqual("Initial Data1 From MainThread", input.Data1);
input.Data1 = "Modified by Thread 1";
input.SomeArray[1] = 11;
Thread.MemoryBarrier();//TMB 1.2 Force storing all written value in order to be visible for the Main threads
Interlocked.Increment(ref finishedCounter);
while (true)
{
//Non-core work (using data specific to this thread)
}
});
var thread2 = new Thread(o =>
{
while (Interlocked.Read(ref ready) != 1)
{
//waiting for initialization
}
var input = (State)o;
//Assert.AreEqual("Initial Data2 From MainThread", input.Data2);
Thread.MemoryBarrier();//TMB 2.1 Force read value pushed from mainThread
//Core work
Assert.AreEqual("Initial Data2 From MainThread", input.Data2);
input.Data2 = "Modified by Thread 2";
input.SomeArray[2] = 22;
Thread.MemoryBarrier();//TMB 2.2 Force storing all written value in order to be visible for the Main threads
Interlocked.Increment(ref finishedCounter);
while (true)
{
//Non-core work (using data specific to this thread)
}
});
thread1.Start(state);
thread2.Start(state);
state.Data1 = "Initial Data1 From MainThread";
state.Data2 = "Initial Data2 From MainThread";
state.SomeArray = new[] { 0, -1, -2 };
Thread.MemoryBarrier();//TMB 0.1 Force storing all written value in order to be visible for the child threads
Interlocked.Increment(ref ready);//child thread will process
while (Interlocked.Read(ref finishedCounter) != 2)//let's wait for the childs threads to finish their core work
{
}
//Assert.AreEqual("Modified by Thread 1", state.Data1);
//Assert.AreEqual("Modified by Thread 2", state.Data2);
Thread.MemoryBarrier();//TMB 0.1 Force retrieving all written value from the child threads
Assert.AreEqual("Modified by Thread 1", state.Data1);
Assert.AreEqual("Modified by Thread 2", state.Data2);
Assert.AreEqual(0, state.SomeArray[0]);
Assert.AreEqual(11, state.SomeArray[1]);
Assert.AreEqual(22, state.SomeArray[2]);
Console.WriteLine("Done");
Console.ReadLine();
}
The questions are :
- Are the asserts on the code above always true ?
- Can the commented asserts on the code above be false?
- Does the the 6 Thread.MemoryBarrier ensure the point i. and ii. above?
- Are the comments on the TMB relevant ?
- Is it architecture dependant ? (I am working on both x86/x64 cpus)