Quantcast
Channel: : software-engineering
Viewing all articles
Browse latest Browse all 19

Threading is Harder Than You Think

$
0
0
I’ve been tinkering with multi-threaded code recently and also re-reading Jeff Richter’s book, CLR via C#. Jeff presents a wonderful example that really illustrates how you can burn your fingers in multi-threaded code. Here’s a slightly modified version of his example:
   1:using System;
   2:using System.Threading;
   3:  
   4:internalclass Program
   5:     {
   6:staticbool StopWorker;
   7:  
   8:staticvoid Main(string[] args)
   9:         {
  10:         Console.WriteLine("Main: letting worker run for 5 seconds");
  11:         var thread = new Thread(worker);
  12:         thread.Start();
  13:         Thread.Sleep(5000);
  14:         StopWorker = true;
  15:         Console.WriteLine("Main: waiting for worker to stop");
  16:         thread.Join();
  17:         Console.WriteLine("Main: Worker stopped");
  18:         Console.Read(); // Let the user view the results before closing the console window.
  19:         }
  20:  
  21:staticvoid worker()
  22:         {
  23:         Int32 x = 0;
  24:while (!StopWorker)
  25:             x++;
  26:         Console.WriteLine("Worker: stopped when x={0}", x);
  27:         }
  28:     }

If you put this code into a console app and run it, you may find it will work. Try setting the build configuration to ‘Release’, then go to the Debug menu and select Start Without Debugging. I’m pretty sure that under those circumstances, the program will appear to hang; the worker thread never exits. The program appears to be correct and works fine under the debugger or in a 64-bit process. Only when it is run as 32-bit and without debugging does it mysteriously fail. Can you see why? The answer is not trivial and requires quite a deep understanding of the compiler.

The key is to understand that compilers make optimizations. In a .NET program, there are 3 possible opportunities for optimization: once in the C# compiler, once in the CLR’s Just In Time (JIT) compiler and then on the CPU itself. In this particular instance, the CLR x86 JIT compiler hoists the test on line 24 out of the loop, since the value is not changed inside the loop then the compiler assumes there’s no point in testing it every time. When running under the debugger, optimizations are disabled so the code works correctly. It also turns out that the 64-bit version of the JIT compiler doesn’t have this particular optimization (it is a less mature compiler) sot he problem doesn’t happen when running as 64-bit.

Those with a C programming background will probably immediately recognise the fix: use of the volatile keyword in line 6, like so:

staticvolatilebool StopWorker;

  
I’ve written many C programs that inspect memory-mapped hardware registers and it is common practice to use the volatile keyword in that situation, but for some reason it had never occurred to me to use it in the above scenario. One might be forgiven for thinking that static variables are thread-safe; how often have you seen this line in the documentation for a class:
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
But of course, this is not a public field, it is private. Plus, thread safety only guarantees the data will not become corrupted, there’s no guarantee of the order of operations or in fact that the operation will not be optimized away.
 
I really like this example because it is a concise, easy to understand demo of the type of subtle bugs that can be introduced by multithreading. In his book CLR via C# , Jeff Richter goes on to explain all the various locking and synchronization mechanism and even offers a few practical solutions to common problems. I highly recommend the book if you don’t already have it and you want a deeper understanding of C# and the .NET Framework.

Viewing all articles
Browse latest Browse all 19

Trending Articles