40

This puzzle was presented at NDC 2010. There are links to video from there, but they are all broken. I don't understand the behavior of this program; why does it hang?

class Woot
{
    private static float PI;
    private static bool initialized = doInitialize();

    private static bool doInitialize()
    {
        if (!initialized)
        {
            var thread = new Thread(() => { PI = 3.14f; });
            thread.Start();
            thread.Join(); // here
        }
        return true;
    }

    public static void Main(string[] args)
    {
        Console.WriteLine(PI);
    }
}

What is the output of this program? Is it:

  • 3.14
  • 0
  • Throws exception
  • None of the above
William
  • 413
  • 9
  • 21
Yola
  • 18,496
  • 11
  • 65
  • 106
  • @Sayse, you mean metadata Type object hasn't been constructed yet when thread tries to use it? That is my guess. – Yola Apr 08 '15 at 06:33
  • 3
    @Yola - you might want to put "*And i can't understand behavior of the program. It hangs.*" at the **end** of your question to not give the game away. :) – Wai Ha Lee Apr 08 '15 at 06:47
  • 1
    @poke That's a totally different question there. Even if the author of the other question meant to ask the same question - this question is more clear. – Sebastian Apr 08 '15 at 07:04
  • @Sebastian The *answer* there explains this question though. – poke Apr 08 '15 at 07:09
  • @poke: No - it does not - it contains the same example but then *asks* the same question without providing the answer to this question. – Sebastian Apr 08 '15 at 07:48
  • 5
    I explain what's going on in this blog post: http://ericlippert.com/2013/01/31/the-no-lock-deadlock/. This puzzle, incidentally, was adapted from a very similar puzzle in Neal's book about Java puzzlers; Java has the same behavior when running static initialization. – Eric Lippert Apr 08 '15 at 15:18
  • I think this is the same as a question I had previously: http://stackoverflow.com/questions/27612772/task-run-in-static-initializer – Timothy Shields Apr 08 '15 at 17:49

3 Answers3

29

I believe that problem is caused by static field initalizator. I've spotted that new thread is started only when doInitialize is done (despite thread.Start() is called) - so I suppose that CLR blocks other threads to avoid concurrent access / double field initalization.

To sum up: Newly-created thread is not started by CLR to avoid concurrent access, but main initalization thread waits for child thread to be done what means deadlock.

Edit

@Sebastian proposed (in a comment) the link that may prove my theory: http://blogs.msdn.com/b/pfxteam/archive/2011/05/03/10159682.aspx

Ehsan Sajjad
  • 61,834
  • 16
  • 105
  • 160
  • 1
    Very good, likely to be right answer, need some proof links. – Yola Apr 08 '15 at 06:50
  • 3
    I think these are the relevant links: [Blog1](http://blogs.msdn.com/b/pfxteam/archive/2011/05/03/10159682.aspx) - [Blog2](http://blog.duncanworthy.me/swdev/csdotnet/constructor-gotchas-part3-deadlock/) and finally [ECMAScript](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf#page=178&zoom=auto,87,610") – Sebastian Apr 08 '15 at 06:55
  • @Yola yes, I'm digging into MSDN to find a prove. The only thing I've found so far is statement, that static field initialization is thread safe, but no statement how thread-safety is ensured. –  Apr 08 '15 at 06:56
  • 6
    Eric Lippert has a blog entry that details exactly that problem, [The no-lock deadlock](http://ericlippert.com/2013/01/31/the-no-lock-deadlock/) – Jean Hominal Apr 08 '15 at 09:24
16

doInitialize is executed when the static type is constructed and then halts until the thread that sets PI terminates.

The thread that tries to set PI however cannot run until the type is initialized, which only happens once the initialization (static constructor and static initializers) are finished—which didn’t happen yet as per above.

So the program deadlocks.

See also this answer by Eric Lippert.

Community
  • 1
  • 1
poke
  • 369,085
  • 72
  • 557
  • 602
  • 1
    This is highly misleading. If static members could not be set until the type initializer completed, then the type initializer could not set static member variables -- clearly false. The fact that `PI` won't be set until after the static constructor completes is an effect of the peculiar arrangement of code in this particular program, by no means a universal statement. It would have been better to say "The code that sets `PI` cannot run..." – Ben Voigt Apr 08 '15 at 15:35
4

Thread will never finish, so thread.Join() will never return. doInitialize() is executed from static constructor. In static constructor we are trying to set the static property, but we can't access the static property unless static constructor is finished. race