static void Main(string[] args) { Thread threadA = new Thread(new ThreadStart(TouchFoo)); threadA.Name = "Thread A"; Thread threadB = new Thread(new ThreadStart(TouchFoo)); threadB.Name = "Thread B"; threadA.Start(); threadB.Start(); threadA.Join(); threadB.Join(); } static void TouchFoo() { Foo.SayHello(); } class Foo { static Foo() { Thread.Sleep(1000); Console.WriteLine("Foo .cctor on thread {0}", Thread.CurrentThread.Name); } static public void SayHello() { Console.WriteLine("Hello From Foo"); } }
In this program, Thread A will probably get the first chance to execute TouchFoo. In doing so, the CLR recognizes the static constructor for Foo has not been executed and sends Thread A into the constructor, which puts the thread to sleep for 1000 ms. The sleeping period allows Thread B to run. Thread B arrives inside of TouchFoo, and again the CLR recognizes the static constructor for Foo has not completed executing. This time however, the CLR knows somebody is working inside of the constructor, and it blocks Thread B until Thread A finishes Foo’s constructor. Typically then, this program would produce the following output:
Foo .cctor on thread Thread A Hello From Foo on thread Thread A Hello From Foo on thread Thread B
No matter how many threads we throw into the mix, the CLR will only allow one thread inside of the static constructor. Of course this isn’t magic, there has to be some thread synchronization (a lock) taken by the CLR before entering the static constructor.
Where there are locks, there is the possibility of a deadlock. So what happens if we try to put the runtime into a bind by feeding it code that should deadlock? Do you think the following program will execute, or will I need to kill it from task manager?
class Class1 { [STAThread] static void Main(string[] args) { Thread threadA = new Thread(new ThreadStart(TouchFoo)); threadA.Name = "Thread A"; Thread threadB = new Thread(new ThreadStart(TouchBar)); threadB.Name = "Thread B"; threadA.Start(); threadB.Start(); threadA.Join(); threadB.Join(); } static void TouchFoo() { string s = Foo.Message; } static void TouchBar() { string s = Bar.Message; } } class Foo { static Foo() { Console.WriteLine("Begin Foo .cctor on thread {0}", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Foo has a message from Bar: {0}", Bar.Message); message = "Hello From Foo"; Console.WriteLine("Exit Foo .cctor on thread {0}", Thread.CurrentThread.Name); } static public string Message { get { return message; } } static string message = "blank"; } class Bar { static Bar() { Console.WriteLine("Begin Bar .cctor on thread {0}", Thread.CurrentThread.Name); Thread.Sleep(5000); Console.WriteLine("Bar has a message from Foo: {0}", Foo.Message); message = "Hello From Bar"; Console.WriteLine("Exit Bar .cctor on thread {0}", Thread.CurrentThread.Name); } static public string Message { get { return message; } } static string message = "empty"; }
Notice in this example, the static constructor for Foo references Bar’s Message property, and vice versa. This produces the following scenario:
Thread A starts and eventually enters the static constructor for Foo. After a writing to the screen, the thread goes to sleep for 5000 ms. Thread B now has a chance to run and eventually enters the constructor for Bar, prints a message and goes to sleep.
Next, Thread A wakes up and finds it needs information from an un-initialized Bar, but it cannot run the constructor because Thread B has it locked. Thread B awakes and needs information from Foo, but it cannot run the Foo constructor because Thread A has it locked. Thread A needs the lock held by Thread B, and Thread B needs the lock held by Thread A. Classic deadlock!
As it turns out, the CLI specification addresses this issue in section 9.5.3.3 of Partition II, entitled “Races and Deadlocks”:
Type initialization alone shall not create a deadlock unless some code called from a type initializer (directly or indirectly) explicitly invokes blocking operations.
So if we don’t have a deadlock, what happens? Here is the output on my machine:
Begin Foo .cctor on thread Thread A Begin Bar .cctor on thread Thread B Bar has a message from Foo: blank Exit Bar .cctor on thread Thread B Foo has a message from Bar: Hello From Bar Exit Foo .cctor on thread Thread A
The message Bar retrieves from Foo is “blank”, but Foo was supposed to initialize the message to “Hello From Foo”. The runtime allowed Thread B to reference Foo before Foo’s static constructor completed! It could have just as easily allowed Thread A to reference Bar before Bar’s static constructor completed, but by letting just one of the threads through, a lock became free and we avoid a deadlock. The runtime cannot perform miracles and let both the constructors run to completion.
The morale of the story is: never touch the static member of another type inside of a static constructor.
C++ programmers knew this was a bad scenario, although for slightly different reasons. See the C++ FAQ for the “static initialization order fiasco”. The punishment for C++ programmers who do this is to take a job at a fast food restaurant, but then hard-core C++ devs are pretty heavy handed (the topic for a future post).