In a class with static fields during application operation, initialization of these static fields occurs only once, which is logical. But hence the question: does the JIT compiler produce something like a boolean variable that shows: has a static constructor already been called or not, and each time it accesses static variables it checks this variable? Does NGen work about the same?
- onewhy should he keep it somewhere? - Grundy
- 2There is not just a boolean variable. There is also a critical section for synchronization :) - Pavel Mayorov
- @Grundy, then check when accessing static variables, probably. I do not know, therefore I asked this question) - Qutrix
- @PavelMayorov, but about it you can read more in Russian somewhere? And it turns out that it’s better not to flirt with static fields, since they incur some overhead? And in general, is it possible to disable it so that I myself can control when and how to initialize? - Qutrix
- 3@Qutrix do not think out. Any class carries "some overhead" regardless of the number of static fields in it. - Pavel Mayorov
1 answer
From Richter , CLR via C # / 4.5, chapter 8, section "Type Constructors":
Calling a type constructor has some peculiarities. When compiling the [any- VladD ] method, the JIT compiler detects the types referenced from the code. If a [static - VladD ] constructor is defined in any of the types, the JIT compiler checks whether the type constructor was executed in the given application domain. If not, the JIT compiler creates a call to the type constructor in the IL code. If the code has already been executed, the JIT compiler does not create a call to the type constructor, since it “knows” that the type has already been initialized.
Then, after the JIT compilation of the method, the [further - VladD ] execution of the flow begins, and eventually the queue reaches the call code of the type constructor. In reality, it may be that several threads simultaneously start executing a method. The CLR tries to ensure that the type constructor is executed only once in each application domain. To do this, when the type constructor is called, the calling thread gets an exclusive lock. This means that if several threads attempt to invoke a type constructor at the same time, only one will receive such an opportunity, and the rest will be blocked. The first thread will execute the static constructor code. After the first thread leaves the constructor, idle threads will “wake up” and check if the constructor has been executed. They will not execute the code again, but simply return control from the constructor method. In addition, on a subsequent call to any of these methods, the CLR will be “aware” that the type constructor has already been executed and will not invoke it again.
In this way:
- the call of the static type constructor C is implicitly included in the native code of the method, for the first time at the execution time mentioning type C in the application, during the JIT compilation of this method
- during execution of the static constructor, the thread gets an exclusive block so that other threads cannot enter the same code
- other methods (or the same) that try to execute the static constructor again will actually miss it, this is the CLR's concern (for example, the JIT-ter can wipe the static constructor code after it has been worked out).
An interesting example from Eric Lippert , showing that ignoring global locking does not always work:
class C { static C() { // Let's run the initialization on another thread! var thread = new System.Threading.Thread(Initialize); thread.Start(); thread.Join(); } static void Initialize() { } static void Main() { } } In this example, a deadlock occurs.
What's happening? When you execute Main , the static constructor is started first. It runs a background thread that executes the Initialize method. When JIT compiles the Initialize method, the JIT-ter sees that the method belongs to class C , that the static constructor is not yet complete, and adds its call into the Initialize method. The Initialize method waits for a global lock to execute a static constructor (more precisely, wait for it to finish to get a fully initialized type). But the global lock will not be released, because the static constructor waits for Initialize() to thread.Join() in thread.Join() .
- apparently it is not necessary to run streams in a static constructor :) - Grundy
- @Grundy: Added an explanation. - VladD