The consequences of running multiple parallel threads?

In the last few hours, I hit that head, so that's it. Maybe this is a common mistake for someone who has little experience with multithreading? Who knows.

In the included code, I create three threads that trigger the DisplayValues ​​(DateTime Now, int Period) method. The debugger stops three times in each of the IF statements, and for each it calls the method with the correct values. The problem is that Console.WriteLine displays erratic values ​​that are completely different from how the calls were made.

The console calls DisplayValues ​​() 3 times with the following parameters, which is correct: DisplayValues('{5/8/2014 4:20:00 AM}', 0); DisplayValues('{5/8/2014 4:35:00 AM}', 1); DisplayValues('{5/8/2014 4:50:00 AM}', 2);

But the conclusion is completely different:

5/8/2014 4:35:00 AM Period: 0

5/8/2014 4:50:00 AM Period: 1

5/8/2014 4:51:00 AM Period: 2

The debugger confirms this. Since this is a console application, I thought it might be that all methods were static, so I moved DisplayValues ​​() to a class. Then I thought that all three instances of the class have the same name, so I changed the name. Then I thought it could be a CancellationTokenSource object, so I also deleted it.

Needless to say, that without threads, the result is correct.

I know that there is an obvious reason, I just don’t know what it is.

Any help is appreciated. Thanks.

 bool thread0Running = false; bool thread1Running = false; bool thread2Running = false; DateTime DateNow = new DateTime(2014, 5, 8, 4, 0, 0); while ((!thread0Running || !thread1Running || !thread2Running) && DateNow.Hour == 4) { if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20)) { thread0Running = true; Class myClass0 = new Class(); new Thread(() => myClass0.DisplayValues(DateNow, 0, cts0.Token)).Start(); } else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 35)) { thread1Running = true; Class myClass1 = new Class(); new Thread(() => myClass1.DisplayValues(DateNow, 1, cts1.Token)).Start(); } else if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 50)) { thread2Running = true; Class myClass2 = new Class(); new Thread(() => myClass2.DisplayValues(DateNow, 2, cts2.Token)).Start(); } DateNow = DateNow.AddMinutes(1); } public void DisplayValues(DateTime Now, int Period, Object obj) { Console.WriteLine(Now.ToString() + " Period: " + Period.ToString()); } 
+6
source share
4 answers

Thread.Start does not mean that the thread starts immediately, it forces the operating system to change the state of the current instance in ThreadState.Running. Once a thread is in the ThreadState.Running state, the operating system can schedule it to run, but this does not mean that the first thread is created first. This is the cause of the problem.

If you want thread 3 to execute sequentially, you should look at thread synchronization.

+1
source

As others have already pointed out, Console.WriteLine can be slower than increasing a variable. Another approach to solving this issue would be to use streams-local variables. Changes to other threads should not be affected. For C #, I found this link: http://msdn.microsoft.com/en-us/library/dd642243 (v = vs .110) .aspx

The advantages of this approach, you do not need to support as many variables as there are threads, and the work of threads can be performed simultaneously.

Good luck

+1
source

I think the reason is that the main thread changes DateNow at the same time as another thread is reading the same value.

Consider this: one of the conditions is true, so a new thread is created, but you cannot control when this thread will be scheduled to run. so at that time - your main thread changed DateNow ... so .. when the newly created thread is actually running - the value it sees and prints is different from the "normal" value that the condition passed ..

Consider this an even stranger confusion: C # provides atomization when writing variables of 32 bits or less

Automation means (in a very general and inaccurate way) that an operation cannot be interrupted in the middle ... All threads get some processor, then the OS stops it and plans to start another thread .. and some time after that - the OS will schedule our thread again and he will continue from where he left off. The atomic operation cannot be stopped in the middle ... it either has not yet begun, or has already been completed.

But the DateTime is actually 64 bits. This means that the OS can interrupt the main thread - in the middle of writing a new value. this means that until the main thread is scheduled again - DateNow will have some strange, inconsistent value - and any other thread can read this value in the meantime.

0
source

Since you are using a lambda expression for your stream function, the DateNow value is not copied until the stream starts executing. Since you do not have synchronization between threads, this is completely unpredictable. The first thread (period 0) probably doesn't get any processor until you start creating a second thread, and then display the current DateNow (4:35). The same thing happens with period 1, and then period 2 finally starts as soon as you come around the loop again after adding 1 minute. Switch each lambda to use its own variable as follows:

 if ((DateNow.Hour == TaskDateTime.Hour) && (DateNow.Minute == 20)) { DateTime DateNow0 = DateNow; // DAteTime is a struct so this is a value copy thread0Running = true; Class myClass0 = new Class(); new Thread(() => myClass0.DisplayValues(DateNow0, 0, cts0.Token)).Start(); } 

Use DateNow1 and DateNow2, the remaining 2 blocks. With this change, I think you will get the result that you expect. A copy of the current DateNow value is now executed at a predictable location in the main thread of execution. However, the order may still not work correctly, since there is no guarantee that the three threads will execute in the order in which they are created.

0
source

All Articles