Help understanding C# and multithreading
Hi I am reading a “threading in C#” tutorial. One of the things it mentions is:
“The CLR assigns each thread its own memory stack so that local variables are kept separate”
And there’s this example:
namespace ConsoleApplication1 {
class Program {
static void Main(string[] args) {
for (int i = 0; i < 20; i++) {
Thread t = new Thread(() => {
Console.WriteLine(i);
});
t.Start();
}
Console.ReadLine();
}
}
}
Output: 1 2 2 4 6 8 10 10 10 10 12 12 14 15 17 18 18 20 20
So the way I understand what’s happening here is:
- The main thread starts executing the for loop.
- A new thread is instantiated and defined such that it will receive the value of “i” and print it to the console.
- The thread instance is started 开发者_高级运维meanwhile the main thread continues working.
Being “i” an integer my guess is that the new thread will have its own copy in its memory stack. And then print the value to the console. But as the results show, it’s skipping values jumping from 10 to 12 or 12 to 14. So is the new thread is receiving a reference to i? But if “i” is an integer shouldn’t the new thread store a new value in its memory stack instead of what seems a reference to i.
Also why are there duplicate values? It’s printing several times 2,10, 12, 18, 20.
Thanks.
That sample is fatally flawed... because every thread is actually sharing the one i
variable. It's being captured by the lambda expression.
This is a very common problem, but it's a real shame to see it in a threading tutorial. (I hope it's not one of my articles! Please tell us where you're reading this.) Eric Lippert has written about it very carefully in his blog posts, "closing over the loop variable considered harmful" - part 1; part 2.
It's worth distinguishing between threading behaviour and that of lambda expressions. Threads really do have their own stacks and their own local variables - but here, i
is shared between all threads due to the lambda expression. It's not a local variable in the "normal" sense.
Here's an example which shows each thread having its own local variables:
using System;
using System.Threading;
public class Test
{
static void Main()
{
for (int x = 0; x < 10; x++)
{
new Thread(Count).Start();
}
}
static void Count()
{
int threadId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Thread {0} starting", threadId);
for (int i = 0; i < 5; i++)
{
Console.WriteLine("{0}: {1}", threadId, i);
}
Console.WriteLine("Thread {0} ending", threadId);
}
}
Each thread will definitely print 0..4 along with its own thread ID. The i
variable is genuinely local to each thread this time - there's no sharing.
When a variable is used in a lambda expression, like your variable i
, it gets hoisted into the a closure (in your case that of the Main method) (first hit on google for "closure c#" happens to be Jon Skeet's article on the subject). And because of this, it is not a local variable and does not live on the thread's stack.
The problem is simple as the tread take time to initialize and run, the i's value will have changed in the mean time. And there is also a possibility of more than one loop would have completed by the time the other threads get a processor cycle to process. Thus a single number gets printed multiple times.
精彩评论