Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array of objects, updating in one thread and reading in another

Simplified the question to give a more clear representation of what I'm actually asking

I have two threads, call them A and B. They share one object of type Foo which has a field called Name and is stored in an array of type Foo[] at index 0. The threads will always access index 0 in a order which is guaranteed by the system already, so there is no race condition of thread B getting before thread A.

The order is this.

 // Thread A
 array[0].Name = "Jason";

 // Thread B
 string theName = array[0].Name

As I said this order is already guaranteed, there is no way for thread B to read the value before thread A

What I want to ensure is two things:

  1. Both threads get the latest object at index 0.
  2. Thread B always gets the latest value in the .Name field

Marking Name as volatile is not an option, as the real objects are a lot more complex and even have custom structs which can't even have the volatile attribute attached to them.

Now, satisfying 1 is easy (always getting the latest object), you can do a .VolatileRead:

 // Thread A
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 obj.Name = "Jason";

 // Thread B
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 string theName = obj.Name

Or you can insert a memory barrier:

 // Thread A
 array[0].Name = "Jason";
 Thread.MemoryBarrier();

 // Thread B
 Thread.MemoryBarrier();
 string theName = array[0].Name

So my question is: Is this enough to also satisfy condition 2? That I always get the latest value from the fields of the object I read out? If the object at index 0 has not changed, but the Name has. Will doing a VolatileRead or a MemoryBarrier on index 0 make sure all the fields IN the object at index 0 also get their latest values?

like image 805
thr Avatar asked Nov 04 '22 09:11

thr


1 Answers

None of these solutions, lock or volatile will solve your problem. Because:

  1. volatile ensures that variables changed by one thread are visible immediately to other threads operating on the same data (i.e. they are not cached) and also that operations on that variable are not reordered. Not really what you need.
  2. lock ensures that the write / read do not occur simultaneously but does not guarantee their order. It depends which thread acquired the lock first, which is non-deterministic.

Therefore, if your flow is:

Thread A read Name
Thread A modify Name
Thread B read Name

exactly in that order, you will need to enforce it with an event (i.e. AutoresetEvent for example):

//Thread A
foo[0].Name = "John"; // write value
event.Set(); // signal B that write is completed

//Thread B
event.WaitOne(); // wait for signal
string name = foo[0].Name; // read value

This guarantees that thread B does not read the Name variable until A has modified it.

Edit: Ok, so you are sure that the above flow is respected. Since you are saying that you cannot declare the fields volatile, I recommend the use of Thread.MemoryBarrier() to introduce fences that enforce ordering:

//Thread A
foo[0].Name = "John"; // write value
Thread.MemoryBarrier();

//Thread B
Thread.MemoryBarrier();
string name = foo[0].Name; // read value

For more info, check this document: http://www.albahari.com/threading/part4.aspx

like image 173
Tudor Avatar answered Nov 11 '22 15:11

Tudor