private InstrumentInfo[] instrumentInfos = new InstrumentInfo[Constants.MAX_INSTRUMENTS_NUMBER_IN_SYSTEM];
public void SetInstrumentInfo(Instrument instrument, InstrumentInfo info)
{
if (instrument == null || info == null)
{
return;
}
instrumentInfos[instrument.Id] = info; // need to make it visible to other threads!
}
public InstrumentInfo GetInstrumentInfo(Instrument instrument)
{
return instrumentInfos[instrument.Id]; // need to obtain fresh value!
}
SetInstrumentInfo
and GetInstrumentInfo
are called from different threads.
InstrumentInfo
is immutable class.
Am I guaranteed to have the most recent copy when calling GetInstrumentInfo
? I'm afraid that I can receive "cached" copy. Should I add kind of synchronization?
Declaring instrumentInfos
as volatile
wouldn't help because I need declare array items as volatile
, not array itself.
Do my code has problem and if so how to fix it?
UPD1:
I need my code to work in real life not to correspond to all specifications! So if my code works in real life but will not work "theoretically" on some computer under some environment - that's ok!
Thread.MemoryBarrier
which will do nothing but add latency. I think we can rely that Microsoft will keep using "strong memory model" in future releases. At least it's very unlikely that Microsoft will change memory model. So let's assume it will not.UPD2:
The most recent suggestion was to use Thread.MemoryBarrier();
. Now I don't understand exact places where I must insert it to make my program works on standard configuration (x64, Windows, Microsoft .NET 4.0). Remember I don't want to insert lines "just to make it possible to launch your program on IA64 or .NET 10.0". Speed is more important for me than portability. However it would be also interesting how to update my code so it will work on any computer.
UPD3
.NET 4.5 solution:
public void SetInstrumentInfo(Instrument instrument, InstrumentInfo info)
{
if (instrument == null || info == null)
{
return;
}
Volatile.Write(ref instrumentInfos[instrument.Id], info);
}
public InstrumentInfo GetInstrumentInfo(Instrument instrument)
{
InstrumentInfo result = Volatile.Read(ref instrumentInfos[instrument.Id]);
return result;
}
This is a question with a long and complicated answer, but I'll try to distill it into some actionable advice.
1. Simple solution: only access instrumentInfos under a lock
The easiest way to avoid the unpredictability in multi-threaded programs is to always protect shared state using locks.
Based on your comments, it sounds like you consider this solution to be too expensive. You may want to double-check that assumption, but if that's really the case, then let's look at the remaining options.
2. Advanced solution: Thread.MemoryBarrier
Alternatively, you can use Thread.MemoryBarrier:
private InstrumentInfo[] instrumentInfos = new InstrumentInfo[Constants.MAX_INSTRUMENTS_NUMBER_IN_SYSTEM];
public void SetInstrumentInfo(Instrument instrument, InstrumentInfo info)
{
if (instrument == null || info == null)
{
return;
}
Thread.MemoryBarrier(); // Prevents an earlier write from getting reordered with the write below
instrumentInfos[instrument.Id] = info; // need to make it visible to other threads!
}
public InstrumentInfo GetInstrumentInfo(Instrument instrument)
{
InstrumentInfo info = instrumentInfos[instrument.Id]; // need to obtain fresh value!
Thread.MemoryBarrier(); // Prevents a later read from getting reordered with the read above
return info;
}
Using the Thread.MemoryBarrier before the write and after the read prevents the potential trouble. The first memory barrier prevents the writing thread from reordering a write that initializes the object's field with the write that publishes the object into the array, and the second memory barrier prevents the reading thread from reordering the read that receives the object from the array with any subsequent reads of the fields of that object.
As a side note, .NET 4 also exposes Thread.VolatileRead and Thread.VolatileWrite that use Thread.MemoryBarrier as shown above. However, there is no overload of Thread.VolatileRead and Thread.VolatileWrite for reference types other than System.Object.
3. Advanced solution (.NET 4.5): Volatile.Read and Volatile.Write
.NET 4.5 exposes Volatile.Read and Volatile.Write methods that are more efficient than full memory barriers. If you are targeting .NET 4, this option won't help.
4. "Wrong but will happen to work" solution
You should never ever rely on what I'm about to say. But... it is very unlikely that you'd be able to reproduce the issue that is present in your original code.
In fact, on X64 in .NET 4, I would be extremely surprised if you could ever reproduce it. X86-X64 provides fairly strong memory reordering guarantees, and so these kinds of publication patterns happen to work correctly. The .NET 4 C# compiler and .NET 4 CLR JIT compiler also avoid optimizations that would break your pattern. So, none of the three components that are allowed to reorder the memory operations will happen to do so.
That said, there are (somewhat obscure) variants of the publication pattern that actually don't work on .NET 4 in X64. So, even if you think that the code will never need to run on any architecture other than .NET 4 X64, your code will be more maintainable if you use one of the correct approaches, even though the issue is not presently reproducible on your server.