views:

975

answers:

3

I am reading http://www.mono-project.com/ThreadsBeginnersGuide.

The first example looks like this:

public class FirstUnsyncThreads {
 private int i = 0;

 public static void Main (string[] args) {
  FirstUnsyncThreads myThreads = new FirstUnsyncThreads ();
 }

 public FirstUnsyncThreads () {
  // Creating our two threads. The ThreadStart delegate is points to
  // the method being run in a new thread.
  Thread firstRunner = new Thread (new ThreadStart (this.firstRun));
  Thread secondRunner = new Thread (new ThreadStart (this.secondRun));

  // Starting our two threads. Thread.Sleep(10) gives the first Thread
  // 10 miliseconds more time.
  firstRunner.Start ();
  Thread.Sleep (10);
  secondRunner.Start ();
 }

 // This method is being excecuted on the first thread.
 public void firstRun () {
  while(this.i < 10) {
   Console.WriteLine ("First runner incrementing i from " + this.i +
                     " to " + ++this.i);
   // This avoids that the first runner does all the work before
   // the second one has even started. (Happens on high performance
   // machines sometimes.)
   Thread.Sleep (100);
  }
 }

 // This method is being excecuted on the second thread.
 public void secondRun () {
  while(this.i < 10) {
   Console.WriteLine ("Second runner incrementing i from " + this.i +
                     " to " + ++this.i);
   Thread.Sleep (100);
  }
 }
}

Output:

First runner incrementing i from 0 to 1
Second runner incrementing i from 1 to 2
Second runner incrementing i from 3 to 4
First runner incrementing i from 2 to 3
Second runner incrementing i from 5 to 6
First runner incrementing i from 4 to 5
First runner incrementing i from 6 to 7
Second runner incrementing i from 7 to 8
Second runner incrementing i from 9 to 10
First runner incrementing i from 8 to 9

Wow, what is this? Unfortunately, the explanation in the article is inadequate for me. Can you explain me why the increments happened in a jumbled order?

Thanks!

+1  A: 

Synchronization is essential when multiple threads are present. In this case you are seeing that both threads read and write to this.i , but no good attempt is done at synchronize these accesses. Since both of them concurrently modify the same memory area, you observe the jumbled output. The call to Sleep are dangerous, it is an approach which leads to sure bugs. You cannot assume that the threads will be always displaced by the inital 10 ms.

In short: Never use Sleep for synchronization :-) but instead adopt some kind of thread synchronization technique (eg. locks, mutexes, semaphores). Always try to use the lightest possible lock that will fulfill your need....

An useful resource is the book by Joe Duffy, Concurrent Programming on Windows.

Francesco
I don't think Thread.Sleep() is used to try and synchronize the threads. It is just used in order for the increments to be observable (otherwise it would just all appear at once on the console), which has a side effect of reducing the chance of a race condition to almost zero.
Allon Guralnek
Yes, the comment says that, but it is actually used as a rough synchronization technique, look at the 10 ms gap between the two calls to Start(). In absence of other techniques, it seems to me that Sleep is faking a synchronization between the two threads.
Francesco
A: 

The increments are not happening out of order, the Console.WriteLine(...) is writing the output from multiple threads into a single-threaded console, and the synchronization from many threads to one thread is causing the messages to appear out of order.

I assume this example attempted to create a race condition, and in your case failed. Unfortunately, concurrency issues, such as a race condition and deadlocks, are hard to predict and reproduce due to their nature. You might want to try and run it a few more times, alter it to use more threads and each thread should increment more times (say 100,000). Then you might see that the end result will not equal the sum of all the increments (caused by a race condition).

Allon Guralnek
Debug.WriteLine?
Henk Holterman
Sorry, Console.WriteLine. Corrected.
Allon Guralnek
+2  A: 

I think the writer of the article has confused things.

VoteyDisciple is correct that ++i is not atomic and a race condition can occur if the target is not locked during the operation but this will not cause the issue described above.

If a race condition occurs calling ++i then internal operations of the ++ operator will look something like:-

  1. 1st thread reads value 0
  2. 2nd thread reads value 0
  3. 1st thread increments value to 1
  4. 2nd thread increments value to 1
  5. 1st thread writes value 1
  6. 2nd thread writes value 1

The order of operations 3 to 6 is unimportant, the point is that both the read operations, 1 and 2, can occur when the variable has value x resulting in the same incrementation to y, rather than each thread performing incrementations for distinct values of x and y.

This may result in the following output:-

First runner incrementing i from 0 to 1
Second runner incrementing i from 0 to 1

What would be even worse is the following:-

  1. 1st thread reads value 0
  2. 2nd thread reads value 0
  3. 2nd thread increments value to 1
  4. 2nd thread writes value 1
  5. 2nd thread reads value 1
  6. 2nd thread increments value to 2
  7. 2nd thread writes value 2
  8. 1st thread increments value to 1
  9. 1st thread writes value 1
  10. 2nd thread reads value 1
  11. 2nd thread increments value to 2
  12. 2nd thread writes value 2

This may result in the following output:-

First runner incrementing i from 0 to 1
Second runner incrementing i from 0 to 1
Second runner incrementing i from 1 to 2
Second runner incrementing i from 1 to 2

And so on.

Furthermore, there is a possible race condition between reading i and performing ++i since the Console.WriteLine call concatenates i and ++i. This may result in output like:-

First runner incrementing i from 0 to 1
Second runner incrementing i from 1 to 3
First runner incrementing i from 1 to 2

The jumbled console output which the writer has described can only result from the unpredictability of the console output and has nothing to do with a race condition on the i variable. Taking a lock on i whilst performing ++i or whilst concatenating i and ++i will not change this behaviour.

AdamRalph