Between the two, Ian Griffiths and Phil Haack have some good info on coding for multithreading. Developing multithreaded applications is hard. It takes experience, which comes in most painful forms of debugging and learning from engineering mistakes — unless you are a multithreading genius who sports a built-in support of multitasking and thread modeling directly in the brain.
Otherwise, every little bit of information helps. Here’s my contribution:
- If you haven’t started coding yet, please spend some time at the whiteboard, modeling your threads and locks. No need to dive into a UML book — simple lines and arrows will suffice. Try to understand when a resource needs locking along the timeline, what possibilities are there for deadlocks and racing conditions. Multiple threads can be easily expressed in two dimensions — the easiest one is a graph where one axis is relative timeline and the other is application scope (local, thread, application, persisted data). Traverse your application data through it and study any point where it deeps below the application scope. There are more elaborate methods of engineering and modeling multithreaded applications, but if you’ve got nothing to begin with, the whiteboard is your best friend.
- If you are dealing with existing code that is not thread-safe, definitely do try replacing your “lock“ statements with Phil’s TimedLock to trace potential deadlocks. You will still suffer extensive debugging therapy, but the TimedLock pill will help a little.
- When writing a type that has to be thread-safe, pay very close attention to every member and how it is used. Consider marking those members that aren’t changed through the lifespan of the instance as “readonly“. This is not going to affect your performance, but will help you organize your type’s members into mutable and immutable piles.
- Speaking of Immutable — this is a good pattern to use as far as thread safety is concerned. It may or may not apply to your scenarios, but should be considered when dealing with types, especially as a complement to Memento and State patterns.
- Just one more on patterns — Proxy is a good way to encapsulate thread-safe access to data that is known to be continually updated.
- Be twice as careful when writing a type that is instantiated statically. Some people mistakenly assume that once they’ve accomplished the feat of thread-safe instantiation, they are out of the woods. Just the opposite — static instances are the marked men of the “racing condition police“.
- Post-constructor initialization of a static member is a bad smell as far as thread safety is concerned. It may be required to implement it in certain circumstances, but should be avoided at all costs.
- Lazy instantiation is not something you apply blindly to any type — it may not save you much performance or memory, but it will make your architecture more complex. If you have to do lazy instantiation, try to use the lockless static instantiation (fifth version on the referenced page).
- According to latest and greatest, just a simple double-lock is not enough. MemoryBarrier is required to ensure correct order of null comparisons.
- Try to be conscious about where and why you place a lock — and what kind of a lock is it. Generally, you’ll either use a Monitor lock (or the lock statement) or the ReaderWriterLock. Ian warns of being too liberal with the use of ReaderWriterLock. Don’t just place a “lock brace“ around your whole method, although you may be tempted to. The longer is the body of the lock, there more possibilities there are that there are other methods and type instances called in that body, and thus the more possibilities there are for a deadlock.
- Just because the method is documented as “thread-safe“ in .NET Reference doesn’t mean that you can’t use it in a non-thread-safe way.
- And last, but not least — make sure you test and debug your multi-threaded application on at least a dual-processor machine.
Like I mentioned before, multithreaded programming is not an easy task. Hopefully, these tips will help you in your exploration of .NET’s and your own potential of enterprise application development.