Multi-threading is an important concept, we can all agree to that.
We could find ourselves using threads even if we're not in an environment which is multi-threaded by nature. (services and such)
But how do we write atomic code? (Thread-Safe)
Well, there isn't a simple answer to that, obviously.
The key thing is: Be aware of any instance used concurrently.
You can read the following post:
Implementing The Singleton Pattern in C#
It describes various ways to implement the singleton pattern specifically, but you can take a lot from it to other scenarios as well.
Plus, there's the MSDN article:
Managed Threading Best Practices
Some key points I pulled out myself:
Singleton Pattern -
If you do use the double-check, you can apply what is described at the bottom.
Instance in concurrent environment -
-
Use Interlocked where possible to ensure thread-safety with performance optimization
-
Perform Locks & Monitoring on reference-type objects (not value-types, not types and such)
Use of Interlocked -
Statement:
lock(lockObject)
{
myField++;
}
Change to:
System.Threading.Interlocked.Increment(myField);
Statement:
if (x == null)
{
lock (lockObject)
{
if (x == null)
{
x = y;
}
}
}
Change to:
System.Threading.Interlocked.CompareExchange(ref x, y, null);
A quick example for implementing locking with a handle object using Interlocked:
public class SyncUsingSyncHandle
{
private object synchHandle;
private object GetSynchHandle()
{
System.Threading.Interlocked.CompareExchange(ref synchHandle,
new object(), null);
return synchHandle;
}
private void DoSomethingSynchronized()
{
lock (GetSynchHandle())
{
//perform logic
}
}
}
Note: You can simply perform lazy instantiation with the handle and lock its instance, but this way you're using Interlocked and its optimizations.
Writing atomic code is certainly a crucial concept when working in concurrent environments, we should "Think Runtime".