2

This is very funny... I would like to understand WHY exactly this is happening.

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start();
 }
}

public void myMethod()
{
 Console.WriteLine(numCounter);
}

The result will vary depending on the moon phase... 3 3 4 4 5 6 7 8 9 10

or even: 1 4 5 5 5 6 7 8 10 10

The question is ... why this is happening ? IF the thread starts after the variable has been incremented, why should it take an unupdated value ????

1
  • This site is for getting reviews of your code. Questions about how code works/why code behaves the way it does, should be asked on Stack Overflow, where I'll now migrate this question. Commented May 14, 2012 at 18:20

4 Answers 4

2

Look at the results carefully. The problem isn't that a thread uses an unupdated value, it's that the value was updated too many times.

For example, in your second example, the events might have gone like this:

Thread 1   Thread 2     Thread 3
Start T2
i++
           WriteLine(1)
Start T3
i++
Start T4
i++
Start T5
i++
                         WriteLine(4)

Although threading can get even more complicated than that, because of compiler and CPU optimizations and CPU caches.

1

The other answers are correct but I want to elaborate on why this happens. Just saying "it is nondeterministic" is correct but does not explain enough.

This behavior happens because the writing processor (your "main" thread) write to its cache line first before flushing to memory. Other threads, the readers, can't peek into the writer's cache line. Only when a flush to memory has happened the data propagates out.

Flushing to memory happens as fast as the CPU can do it but if the updates happen quickly in succession some will be merged into a single store. That's why numbers are missing - their writes have been merged.

2
  • I think exactly the same behavior could be observed even if there was no cache: this behavior could be cause by timing alone. Commented May 15, 2012 at 9:44
  • Yes but it likely wasn't. I tried to explain how it really works, not how it could work. Commented May 15, 2012 at 10:18
0

As far as I can see, this is completely non-deterministic. You could get values all the way from ten zeroes being written all the way up to ten tens and all incrementing variations in between.

The why is that you do not have any sort of synchronized and/or serialized access to your numCounter variable. The threads will read it only when the thread is executing, which could happen at any time, based on any number of environmental conditions.

The quick way to get all numbers, with no repeats would be thus:

public int numCounter;

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  Thread myThread = new Thread(myMethod);
  myThread.Start(numCounter);
 }
}

public void myMethod(object numCounter)
{
 Console.WriteLine(numCounter);
}

The main downsides here are 1) the signature of myMethod has to change, 2) boxing occurs when converting numCounter from an int to an object and 3) this still doesn't guarantee the output is in the order 0..10 that may be expected.

Here's a slightly different version which uses a delegate and BeginInvoke/EndInvoke (the way I like doing threading). It eliminates downside #2 from above, but still keeps #1 and #3:

public int numCounter;

private delegate void MyMethodDelegate(int numCounter);

private void button2_Click(object sender, EventArgs e)
{
 for (numCounter = 0; numCounter < 10; numCounter++)
 {
  MyMethodDelegate myDelegate = new MyMethodDelegate(myMethod);
  myDelegate.BeginInvoke(numCounter, myMethodDone, myDelegate);
 }
}

public void myMethod(int numCounter)
{
 Console.WriteLine(numCounter);
}

public void myMethodDone(IAsyncResult result)
{
 MyMethodDelegate myDelegate = result.AsyncState as MyMethodDelegate;

 if (myDelegate != null)
 {
  myDelegate.EndInvoke(result);
 }
}
6
  • How could he get ten zeroes? The threads are started only after the counter is incremented. Commented May 14, 2012 at 16:50
  • Ok, scratch that. But the first thread is kicked off while the counter is still zero though. Commented May 14, 2012 at 16:52
  • Strictly speaking, each thread can print any value between (inclusive) the value of the counter at which it was started up to 10. Commented May 14, 2012 at 17:45
  • Also, another way of eliminating the non-determinism is to pass the counter into the thread method, either via ParameterizedThreadStart or through a delegate/lambda closure. Commented May 14, 2012 at 17:52
  • @DanLyons, using closures here wouldn't solve anything, there is only one loop variable and it would be shared among the threads. Commented May 14, 2012 at 17:54
0

Just because the thread starts with an updated value does not guarantee that the value remains the same by the time it hits the code where the value is read. Depending on what is going on in your system, you could have several iterations go by in your loop before you hit the Console.WriteLine.

If you want each thread to have a different value, the best way to do that would be to declare an integer in your loop, and initialize it to numCounter. Then use a ParameterizedThreadStart in your thread, and pass it the local value. Or, one thing I do is declare a delegate (you still have to use a variable scoped to your loop in this option as well) inline that will do the work, in order to avoid boxing/unboxing of the integer.

for (int i = 0; i < 10; i++){
    var tmp = i;
    new Thread(() => {
        Console.WriteLine(tmp);
    }).Start();
}

You would also want to keep track of the states of your threads in a non-hypothetical setting, so that you don't exit before all your threads are done working. If you don't want to use the ThreadPool, then declare an array of WaitHandles the size of your loop, and then call WaitArray[tmp].Set() after your threaded code has finished executing. After the loop, you would then have a WaitHandle.WaitAll(WaitArray) statement to block until they are all finished. Probably easier and safer to just use the ThreadPool, and its associated callback mechanism, though.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.