8 Replies - 701 Views - Last Post: 22 February 2012 - 12:13 AM Rate Topic: -----

#1 scolty  Icon User is offline

  • D.I.C Regular

Reputation: 3
  • View blog
  • Posts: 259
  • Joined: 27-April 11

For loop local variable.

Posted 21 February 2012 - 02:40 AM

Morning guys,

I know how to solve this issue, i just dont understand what is actually happening and id appreciate it if someone could enlighten me.

static void Main()
        {
            Go();
            Console.ReadLine();
        }

        static void Go()
        {
            for (int i = 0; i < 10; i++)
            {
                Thread t = new Thread(() => Console.Write(i));
                t.Start();
            }
        }


Output = 0123345588 basically a mess!
i know the issue is that the variable i is refering to the same memory location throughout the loops lifetime. The solution is to assign a temp variable inside the for loop
for(....)
{
    int temp = i;
    Thread t = new Thread(() => Console.Write(temp));
...



Question 1:

Quote

Variable temp is now local to each loop iteration. Therefore, each thread captures a different memory location and there’s no problem
Ive looped through the code, and temp still remembers what its previous value is. (Guess) am i creating a new temp on the stack each loop, and GC is wipping the old one out?

Thanks

Is This A Good Question/Topic? 0
  • +

Replies To: For loop local variable.

#2 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5595
  • View blog
  • Posts: 11,970
  • Joined: 02-June 10

Re: For loop local variable.

Posted 21 February 2012 - 07:15 AM

View Postscolty, on 21 February 2012 - 03:40 AM, said:

(Guess) am i creating a new temp on the stack each loop, and GC is wipping the old one out?


In a word: Yes.

The instance of temp is no longer reachable through code once a new instance is created. The reference 'temp' is re-pointed to the new memory address. When the entire method is done, all these instances eventually go out of scope, then GC eventually cleans it all up when it feels like it.
Was This Post Helpful? 1
  • +
  • -

#3 scolty  Icon User is offline

  • D.I.C Regular

Reputation: 3
  • View blog
  • Posts: 259
  • Joined: 27-April 11

Re: For loop local variable.

Posted 21 February 2012 - 07:21 AM

Thanks for replying.

So just to clarify. If the size of my stack was very small, could i run out of memory if i had an extremely large number of itterations in my for loop, due to it effectively recreating the temp variable each loop?

or have i misunderstood?
Was This Post Helpful? 0
  • +
  • -

#4 negligible  Icon User is offline

  • D.I.C Regular

Reputation: 62
  • View blog
  • Posts: 302
  • Joined: 02-December 10

Re: For loop local variable.

Posted 21 February 2012 - 07:47 AM

-removed-

This post has been edited by negligible: 21 February 2012 - 11:06 AM

Was This Post Helpful? 0
  • +
  • -

#5 tlhIn`toq  Icon User is offline

  • Please show what you have already tried when asking a question.
  • member icon

Reputation: 5595
  • View blog
  • Posts: 11,970
  • Joined: 02-June 10

Re: For loop local variable.

Posted 21 February 2012 - 07:48 AM

I'm going to suggest that experimentation is the better teacher than someone just telling you.

You already have a program set up for this. Just increase the loop from 10 to 10,000 and see what it does.

You have several factors at play here. One of which is how many threads will your computer allow one application to hold at a time? After all, your scenario here is based on making new threads. So if you can't make more than 64 concurrent threads then you can't make more than 64 concurrent instances of 'temp'. Right? As old threads die, new threads are released and your code continues. But your threads have a very short lifespan because they do next to nothing.

Then comes the GC. It cleans up whenever it feels like it.

Your computer will react differently than mine.

LOTS of factors.

The best advice is to program defensively and make sure you are prepared for less than "ideal, clean room, test bench" conditions.

For example, I have an application that searches the local network to see how many computers are available. It spawns threads as far as it can. SO a loop in a loop in a loop type of deal. While trying to check IP addresses from 0-255 it might spawn 30 threads and wait for them all to report back, then another 30. Or it might do 64 at a time.

I do the same thing with some photo processing software I created. I monitor the amount of free memory and only handle as many concurrent photos as I reasonably can. Not an arbitrary number like 10 or 100. But as many as can be done on X number of processors with Z amount of RAM.

It takes a lot of trial and error to work out the formulas for things like this. But the results are worth it to watch a program dynamically make full use of all the resources it can to speed up processing. Its nice to set up the cheap PC a client's bookkeeper wants to buy, and a good machine side-by-side and show them the money spent on a quality machine will be money well spent.
Was This Post Helpful? 1
  • +
  • -

#6 AdamSpeight2008  Icon User is offline

  • MrCupOfT
  • member icon


Reputation: 2268
  • View blog
  • Posts: 9,482
  • Joined: 29-May 08

Re: For loop local variable.

Posted 21 February 2012 - 08:29 AM

All bets are off when thread shared state,without some form concurrency control.

What did you expect the output be in the first place?
Was This Post Helpful? 0
  • +
  • -

#7 scolty  Icon User is offline

  • D.I.C Regular

Reputation: 3
  • View blog
  • Posts: 259
  • Joined: 27-April 11

Re: For loop local variable.

Posted 21 February 2012 - 09:15 AM

@Adam: I was just a bit surprised that including the local variable temp actually solved the issue and i then got a proper sequence of numbers.

@Clint and Negligible, i changed the code to use decimals and am running it now while i have task manager open. The total number of threads is constant but the memory is ... going down like the Titanic. (Stopped it at 50k)

[Question] why is the thread count constant? i thought that would have increased(+ assisting in eating up my memory since each thread has a user-mode stack = 1MB + a couple other things) as well until it reached a max and then threw an OutOfMemoryException. Max being 1361 threads since im running a 32bit processor.

Scroll to 4min 10sec

Thanks for ur help.

This post has been edited by scolty: 21 February 2012 - 09:22 AM

Was This Post Helpful? 0
  • +
  • -

#8 CodingSup3rnatur@l-360  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 992
  • View blog
  • Posts: 972
  • Joined: 30-September 10

Re: For loop local variable.

Posted 21 February 2012 - 03:31 PM

The code you have posted isn't so much an exercise in threading as it is an exercise in demonstrating captured variables, and to really understand the code, you need to know how lambdas use captured variables.

In actual fact, as this lambda expression:

() => Console.WriteLine(temp);


'captures' the variable temp, temp should be allocated on the heap.

A captured variable is a variable that an anonymous method/lambda uses from an enclosing scope (the scope surrounding the lambda that isn't strictly its own scope).

Take this method (taking out any threading code for clarity):

Spoiler


In this code, I define a list of delegates. I then loop from 0 to 9, adding a lambda that prints the current value of 'i' (in theory, at least) to this list. So,after the for loop, I have a list of 10 delegates. I then loop through that list, and execute each delegate, which print out an integer from each invocation.

What would you expect to be printed to the console?

Spoiler



How can we explain this result?

Well, we first have to understand that the variable i is captured by the lambdas we create.

Think of what a lambda expression (simply speaking) is. It is a method. So, Console.WriteLine(i) in the above code is simply the body of a method, and so the lambda is analogous to this when logically expanded:

Spoiler


Looking at it like this, you should immediately see something slightly...odd. You are using the variable i inside your lambda method. However, you don't pass it in as a parameter, yet you are still allowed to use it in you lambda. How can that lambda method use a variable that hasn't been declared either via a parameter or local variable in the lambda method? We are using the variable i from the Go method. Surely that violates one of the first things we learn in C# - one method cannot directly access the local variables of another method.


To allow this behaviour, what actually happens behind the scenes is that the compiler generates a brand new class that is used to hold the captured variables (i, in this example) (remember that captured variables are any variables used in the lambda that the lambda doesn't declare), and an instance of that class is made available to the lambda method, so that it has access to i.


So, taking my code above, the compiler sees that I have used a variable i defined in the enclosing method (Go()) in my lambda. Therefore, to give me access to that variable from inside the lambda, it creates a new class that will look something similar to this:

Spoiler


Immediately, you can see that the loop variable i has been moved into this new class as an instance field, and the body of my lambda has being moved into this new class as a fully fledged method that uses the instance field i. An instance of that class is created in the Go method, and is used to hold the variable i, and execute in the code in the body of the lambda.

So, once the compiler has finished with our Go method:

Spoiler


you can think of it as actually looking something like this:

Spoiler


If you run that, you see that you get the list of 10s printed out. Here, the variable i always refers to the same memory location on the heap. We create one instance of the CompilerGeneratedClassName, and therefore there is only ever one i on the heap. Therefore, when we get around to executing each of the delegates in the list, they are all pointing to that same memory location, and as the for loop has already run and incremented the value at that location to 10, we get a 10 printed out for every delegate invocation.


Now, let's introduce a temp variable, as so:

Spoiler


in order to get an understanding of how temp fixes this problem.


Now, we get the expected/desired result. Why does temp make a difference? Well, temp is said to be instantiated on every iteration of the loop.


The compiler generated class will be almost identical (there needs to be a compiler generated class still, as the lambda is capturing a variable from its enclosing scope still (before that variable was i, this time it is temp)), except it will contain the temp variable rather than i:

Spoiler


Essentially, you can think of the compiler generated code for the Go method as now looking more like this:

Spoiler


Notice how an instance of the compiler generated class is now created on EVERY iteration. Therefore, at the end of the loop, you have 10 different objects on the heap, each with their own temp instance variable. The first object has its temp set to 0, the second has it set to 1, the third set to 2 and so on. Each lambda is now referring to a different memory location, rather than the same memory location as before. Hence, we get the expected/desired output. Each lambda basically gets its own instance of the compiler generated class, and so each has a unique temp variable

Also, from this we can see that variables captured by lambda expression will be allocated on the heap (as they will become part of the compiler generated class, and classes get allocated on the heap when they are instantiated). This is true for your temp variable in your code.

Here is what the final code will look like in the threading context:


Without 'temp'

Spoiler


With 'temp'

Spoiler



Quote

(Guess) am i creating a new temp on the stack each loop, and GC is wipping the old one out?


The garbage collector doesn't deal with the stack, it deals with the heap. To 'clear' a variable from the stack technically need only be a matter of moving a pointer. Further, I have addressed the temp variable issue above.


Quote

i changed the code to use decimals and am running it now while i have task manager open. The total number of threads is constant but the memory is ... going down like the Titanic. (Stopped it at 50k)


That may be because your threads are doing next to nothing, and so are dieing almost immediately after being created. Try this code:

Spoiler


If you are running from Visual Studio, keep an eye on the number of threads in the vshost.exe process associated with the running application, after you press enter to start the thread create loop. You should see (at least for the first 10 seconds or so) the number of threads increasing incrementally, at a rate of approximately one every second.

Any questions, just ask :)

This post has been edited by CodingSup3rnatur@l-360: 16 June 2012 - 03:06 AM
Reason for edit:: Added summary for threading context

Was This Post Helpful? 3
  • +
  • -

#9 scolty  Icon User is offline

  • D.I.C Regular

Reputation: 3
  • View blog
  • Posts: 259
  • Joined: 27-April 11

Re: For loop local variable.

Posted 22 February 2012 - 12:13 AM

Morning, thanks very much!! I wasnt aware that it was actually creating a completely new class behind the scenes, but it makes sense now.

Thanks again bro
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1