This ‘helpful’ (using this term loosely) post is for those you considering adding multi-threaded code to your application. The intended audience is developers with less than mastery-level (10+ years) multi-threading experience.
The first question your up against is:
Do I need multi-threading in my application?
The answer is an emphatic NO you don’t. If you accept this answer you can quit reading now; otherwise I have a different question for you:
Would you be willing to voluntarily walk across across 100 yards of molten lava, bare foot, carrying a 250lb backpack, uphill, with a blind fold on, all the while ignoring the perfectly good bridge over the lava you simply ignored because it was too easy?
Seriously though, avoid at any cost as much as you can adding multi-threading code.
Common ramifications of threading
- Most applications that employ threading find a great majority of their bugs in the threading code.
- Almost always the difficult/impossible to reproduce issues stem from broken threading.
- More often that not, the application actually performs worse once you start multi-threading.
- Most of the time threading wasn’t truly *required* in the first place.
If you still think multi-threading isn’t that hard read this article on a simple thing like initializing a static variable in a thread-safe manner: http://www.yoda.arachsys.com/csharp/singleton.html
So you still want to do multi-threading?
Well, I tried and failed. Now you need to go learn EXACTLY what the following are used for and when/why you would use them (and generally in this order):
- Thread
- ThreadPool
- ManualResetEvent
- AutoResetEvent
- EventWaitHandle
- WaitHandle
- Monitor
- Mutex
- Semaphore
- Interlocked
- BackgroundWorker
- AsyncOperation
- lock Statement
- volatile
- ThreadStaticAttribute
- Thread.MemoryBarrier
- Thread.VolatileRead
- Thread.VolatileWrite
While writing anything thread-safe, ask yourself the following questions:
- What can happen if two threads are executing each statement in step with each-other?
- Do I really need this lock()?
- You need a lock if you are going read AND write to memory that other threads will also access.
- Consider using Interlocked instead.
- What’s the least amount of code that MUST be in the lock()?
- Can I make this object/state immutable and avoid the locking?
- What’s the harm if I don’t lock it?
- Is there any way for a caller to have code execute inside the lock?
- Do not call delegates inside a lock unless you created it.
- Do not call methods/properties inside a lock on a class you didn’t create or don’t control.
- Never invoke to a Control from within a lock.
- Review all members accessed and changed inside a lock for one of these:
- Was the variable declared ‘volatile’?
- Did I use Thread.VolatileRead/Write?
- Did I use Thread.MemoryBarrier?
- Are you using System.Threading.Thread.MemoryBarrier correctly?
- If multiple locks are involved are they always obtained in the same order?
- Don’t use a private member (or expose if you must) to synchronize as this prevents ordering locks.
Things you may not need to make thread-safe:
1. Quite commonly you find people locking on initialization of a property when performing a lazy-load:
private volatile object _data; public object Data { get { if (_data != null) return _data; lock (this) { if (_data == null) _data = new object(); } return _data; } }
This is usually not necessary since the worst that happens is the data is loaded twice. It depends greatly on what that object is and can do, but usually it’s safe to load two instances. Think about it without concern for performance (the likely-hood is too small to be a performance impact). If you can load/create the data twice without causing issue then just do this instead:
public object Data { get { return _data ?? _data = new object(); } }
2. Consider pre-fetching instead of lazy loading with shared state. This can then be performed in the constructor which is only on a single thread.
3. Immutable objects (objects that do not change state) do not normally need locks to access.
4. Fire-and-forget objects work best, start a task and don’t wait for it. You defeat the purpose of multi-threading with synchronization.
And finally, yes you should test it but don’t depend upon it. Get the whole team together if needed to do a line-by-line walk of the code. I’m sure I’ve missed a lot of things here, but this should be a good start.