none
managed thread RRS feed

  • Question

  • I am studying for the 70-483 exam and one of the examples has me confused.  I cannot tell how the following code determines when to stop i.e. (how is _f.value is determined)

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        class Program
        {
            public static ThreadLocal<int> _f = new ThreadLocal<int>(() =>
               {
                   return Thread.CurrentThread.ManagedThreadId;
               });
            static void Main()
            {
                new Thread(() =>
                {
                    for (int x = 0; x < _f.Value; x++)
                    {
                        Console.WriteLine("A: {0}", x);
                    }
                }).Start();
                new Thread(() =>
                {
                    for (int x = 0; x < _f.Value; x++)
                    {
                        Console.WriteLine("B: {0}",x);
                    }
                }).Start();
                Console.ReadKey();
            }
        }
    }
    

    Tuesday, October 23, 2018 4:39 PM

Answers

  • I think the best approach to answering this is to have you debug it. Put a breakpoint on the following lines.

    - return Thread.CurrentThread.ManagedThreadId

    - start of Main (the open curly)

    - Console.WriteLine in each for loop

    Then run your code (F5).

    The first breakpoint you'll hit is the Main because the runtime starts there. But what you didn't see was that the _f field in your Program class was initialized to new ThreadLocal<int>. This happens because the runtime guarantees that static fields are initialized at some point before the type they contain is used the first time. Since you put it in Program, the initialization will occur before Main is called.

    At this point _f is initialized but you passed a function to the constructor and (for ThreadLocal) the provided function hasn't run yet. ThreadLocal is evidently lazy loading the value when it is given a function, which makes sense.

    You are now sitting on the new Thread (first time) call. At this point it will create an instance of Thread and pass your lambda function to it. Again, the function doesn't actually execute yet because this is a thread. However you have combined the creation with a call to Start so what you'll see happen is that the thread will get created and then immediately it will start execution.

    Because Windows is responsible for scheduling threads you may or may not immediately jump to the for loop inside. It depends completely on the scheduler. You will either step to the next new Thread call (and then Start) or you may jump into the function you passed to the thread. At the point you called Start (the first time) execution of the current (Main) thread and your first child thread can be interleaved. So you'll find you may be bouncing back and forth across threads. Again, completely depends upon the scheduler.

    At the latest, when you get to ReadKey your main thread is blocked and your 2 child threads are running. Both of them go into a for loop that sets x to 0 and then needs the value of _f. The moment they call the Value property on this instance lazy loading kicks in and your function that you originally passed to the constructor for _f executes. Hence you'll that breakpoint. This code is running on a secondary thread so the Id you get will be whatever ID the runtime assigns to it. Remember that you have 2 threads running so whether this is thread "A" or "B" is completely arbitrary. It could be either one.

    When you continue running again you will either hit the WriteLine for the thread that just initialized its _f value or you'll get thrown back into the ThreadLocal function again. If the former then that is the thread that was initializing _f and the scheduler hasn't yet let the other thread run so you're inside the for loop. The code will execute as normal from here on out.

    If/when you get thrown back into the ThreadLocal call you're now on the other thread. This is, again, because of the _f.Value expression in the (other) thread's for loop. Once again the value gets initialized with the current thread's Id. After this you'll keep hitting the Console calls as the for loops execute and these will be mixed between the 2 threads.

    At some point the threads finish. Meanwhile the main thread is still blocked waiting for a key to be pressed.

    The difference between this execution and what you might see with some other static field is the ThreadLocal part. Normally fields are stored in global memory and shared across all threads. Indeed the _f field is the same way. What is different is how the Value property is implemented. You can look at the definition yourself if you like. ThreadLocal is designed to allow each thread to have its own "value". So the property, rather than using some field contained in the ThreadLocal class, fetches the value from thread local storage (TLS) which is a per-thread memory block that the OS sets up. If it doesn't find a value then it calls the function you passed to the constructor to initialize it. It then stores the value into TLS. Thereafter it is just retrieving the value from TLS. Since each thread has its own storage you (can) get different values for each thread.



    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, October 24, 2018 2:03 PM
    Moderator

All replies

  • The program itself stops when you press a key. The Main method starts 2 threads (.Start calls). Then it blocks waiting for you to press a key via Console.ReadKey. As soon as you press a key the Main method ends. At that point the console window should go away. However the threads that were created are still running. Since they were not marked as background threads they will continue to run until they complete. This is how Windows deals with thread of a process. If they had been marked as a background thread then when the main thread terminates they would have been terminated by Windows automatically.

    Within each thread is a simple for loop that counts from 0 to _f.Value. _f is a thread local variable which means it is per-thread. Each thread gets its own ID so each thread will count up to whatever ID it was assigned. Then it would terminate. Because the referenced field is a thread local variable its actual value is stored per-thread.

    Seriously I have no idea what this example is supposed to be teaching you. If I ever saw this kind of code in the wild I would have rewritten it. 


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, October 23, 2018 6:03 PM
    Moderator
  • I did not phrase correctly.  You are correct, the program stops when a key is pressed.  What I should have asked was when does the for loop stop as I do not understand how _f.value ever got populated. The only thing I can think of is the physical address of the variable was already populated but the book actually showed the results so that does not make sense

    Wednesday, October 24, 2018 9:27 AM
  • The _f variable is a static variable that is initialized to a new ThreadLocal<int> object.

    The constructor for the ThreadLocal object takes a method. If we look at the documentation for this here, you can see that this method is called the first time that the value of the ThreadLocal object is requested (an example of lazy initialisation).

    So...as soon as the first instance of Program is created (on startup), the _f variable will be initialised to a new ThreadLocal<int> object.

    The first time that _f.Value is requested (in each of the for loops, because its on a per-thread basis) the method passed to the ThreadLocal constructor is called which returns the thread ID.

    Wednesday, October 24, 2018 10:19 AM
  • Since the results show the max value of A = 2 and B = 3, then _f.value must have been 2 when the first thread was initialized and 3 for the second.  How is that possible since _f is never set to a value nor incremented?
    Wednesday, October 24, 2018 10:36 AM
  • I think the best approach to answering this is to have you debug it. Put a breakpoint on the following lines.

    - return Thread.CurrentThread.ManagedThreadId

    - start of Main (the open curly)

    - Console.WriteLine in each for loop

    Then run your code (F5).

    The first breakpoint you'll hit is the Main because the runtime starts there. But what you didn't see was that the _f field in your Program class was initialized to new ThreadLocal<int>. This happens because the runtime guarantees that static fields are initialized at some point before the type they contain is used the first time. Since you put it in Program, the initialization will occur before Main is called.

    At this point _f is initialized but you passed a function to the constructor and (for ThreadLocal) the provided function hasn't run yet. ThreadLocal is evidently lazy loading the value when it is given a function, which makes sense.

    You are now sitting on the new Thread (first time) call. At this point it will create an instance of Thread and pass your lambda function to it. Again, the function doesn't actually execute yet because this is a thread. However you have combined the creation with a call to Start so what you'll see happen is that the thread will get created and then immediately it will start execution.

    Because Windows is responsible for scheduling threads you may or may not immediately jump to the for loop inside. It depends completely on the scheduler. You will either step to the next new Thread call (and then Start) or you may jump into the function you passed to the thread. At the point you called Start (the first time) execution of the current (Main) thread and your first child thread can be interleaved. So you'll find you may be bouncing back and forth across threads. Again, completely depends upon the scheduler.

    At the latest, when you get to ReadKey your main thread is blocked and your 2 child threads are running. Both of them go into a for loop that sets x to 0 and then needs the value of _f. The moment they call the Value property on this instance lazy loading kicks in and your function that you originally passed to the constructor for _f executes. Hence you'll that breakpoint. This code is running on a secondary thread so the Id you get will be whatever ID the runtime assigns to it. Remember that you have 2 threads running so whether this is thread "A" or "B" is completely arbitrary. It could be either one.

    When you continue running again you will either hit the WriteLine for the thread that just initialized its _f value or you'll get thrown back into the ThreadLocal function again. If the former then that is the thread that was initializing _f and the scheduler hasn't yet let the other thread run so you're inside the for loop. The code will execute as normal from here on out.

    If/when you get thrown back into the ThreadLocal call you're now on the other thread. This is, again, because of the _f.Value expression in the (other) thread's for loop. Once again the value gets initialized with the current thread's Id. After this you'll keep hitting the Console calls as the for loops execute and these will be mixed between the 2 threads.

    At some point the threads finish. Meanwhile the main thread is still blocked waiting for a key to be pressed.

    The difference between this execution and what you might see with some other static field is the ThreadLocal part. Normally fields are stored in global memory and shared across all threads. Indeed the _f field is the same way. What is different is how the Value property is implemented. You can look at the definition yourself if you like. ThreadLocal is designed to allow each thread to have its own "value". So the property, rather than using some field contained in the ThreadLocal class, fetches the value from thread local storage (TLS) which is a per-thread memory block that the OS sets up. If it doesn't find a value then it calls the function you passed to the constructor to initialize it. It then stores the value into TLS. Thereafter it is just retrieving the value from TLS. Since each thread has its own storage you (can) get different values for each thread.



    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, October 24, 2018 2:03 PM
    Moderator
  • Dude, thanks for the detailed answer!  Much appreciated
    Wednesday, October 24, 2018 2:05 PM