Recently I have been involved with a multi-threading application, and throughout development I have been acquiring tips and gotchas here and there. One of the fun things about such applications is that you get to play with some sort of almighty powerful server box. In my case, a cool 8-processor dual core box with 8 GB RAM... How much is it? $70,000. =)
Test your application in a comparable multi-processor testing server. Better yet, test it on your production box
Yes, you heard me. Usually multi-threading applications are performance critical. Otherwise how else can you justify a pricy machine and more complicated code? If you are not able to perform testing on the actual production box, chances are the users will ultimately find the problems. Which means developers, QA, and project managers all have to work OT hours to fix the problem and push those fixes to production, plus the number of phone calls and help desk tickets you have to make. When you multiply these hours and stress, the justification of having another pricy testing server before going into production all of a sudden is not too pricy anymore.
Server GC vs. Workstation GC
.NET garbage collection behaves differently when it is installed on a single processor machine versus on a multi-processor machine. If you install it on a single processor box, it is in Workstation GC mode, meaning there is at most one thread doing garbage collection. When you install .NET onto a multi-processor box, it is installed as Server GC mode, meaning there is one GC thread per CPU. I strongly advise that the environment you deploy to before your production environment to be a multi-processor machine and have Server GC mode turned on. That way you are testing more closely to the real environment. In performance-intensive applications, garbage collection, albeit automatic, is usually worth monitoring as well.
Raise an event thread-safe
In C#, many of us have raised an event this way. In fact, this is the way most books/lectures/tutorials demonstrate it.
1 public class Battery
2 {
3 public const int LOW_BATTERY_LEVEL = 20;
4
5 public event EventHandler LowBattery;
6 public event EventHandler Depleted;
7
8 private int remainingBattery = 100;
9
10 private void OnLowBattery()
11 {
12 if (LowBattery != null)
13 {
14 LowBattery(this, EventArgs.Empty);
15 }
16 }
17
18 private void OnDepleted()
19 {
20 if (Depleted != null)
21 {
22 Depleted(this, EventArgs.Empty);
23 }
24 }
25 }
Did you know that this is not thread-safe? Thread A could be executing this code, does a null check at line 12, when it is just about to raise the event, Thread B grabs the subscriber and unregisters/unwires the event. When Thread A resumes execution, it tries to raise the event when no one is subscribing to it. Null reference exception.
In order to make the code thread-safe, we can take advantage of delegates are value-types, in other words, you can only create"copies" of them, but not by reference.
10 private void OnLowBattery()
11 {
12 EventHandler handler = LowBattery;
13 if (handler != null)
14 {
15 handler(this, EventArgs.Empty);
16 }
17 }
Use of lock(this) and lock(typeof(Foo))Every C# programmer is aware of the lock keyword. It prevents multiple threads from referencing the resource while the resource is being modified. When you want to modify a member variable thread-safe, you can go and lock the object that contains it:
public Foo()
{
lock(this)
{
_memberVariable = "SOMETHING";
}
}
But what about static variables? Well, interestingly the lock statement allows you to lock a type object as well:
public class Foo
{
public static string StaticVariable = null;
public void SetUpStatic()
{
lock (typeof(Foo))
{
StaticVariable = "SOMETHING";
}
}
}
Give you threads namesYes. Threads in the old days have only a thread id, which gets generated every time when a new thread is genereated, and that makes debugging very painful. Now in .NET you can programmatically give each thread your application creates a name with its t.Name property. Use them, then in your VS.NET Debug/Windows/Threads (Ctrl-Alt-H while debugging) you will see each thread with their name. Now they look much more friendly. Better yet, name your threads after each project manager you have worked with =)
Use .NET synchronization classesDon't tell people you know how to write multi-threading applications knowing only how to use lock(). If you don't know how Monitor.Wait() and Monitor.Pulse() and the shopping bag of the synchronization classes that .NET provides, you are missing out alot. As a starter, try this excellent article
here. It's a must read for anyone programs C# and multi-threading.