www.gibmonks.com

Table of Contents




Previous Page
Next Page

Recipe 18.14. Performing Atomic Operations Among Threads

Problem

You are operating on data from multiple threads and want to insure that each operation is carried out fully before performing the next operation from a different thread.

Solution

Use the Interlocked family of functions to insure atomic access. Interlocked has methods to increment and decrement values, add a specific amount to a given value, exchange an original value for a new value, compare the current value to the original value, and exchange the original value for a new value if it is equal to the current value.

To increment or decrement an integer value, use the Increment or Decrement methods, respectively:

	int i = 0;
	long l = 0;

	Interlocked.Increment(ref i); // i = 1
	Interlocked.Decrement(ref i); // i = 0
	Interlocked.Increment(ref l); // l = 1
	Interlocked.Decrement(ref i); // l = 0

To add a specific amount to a given integer value, use the Add method:

	Interlocked.Add(ref i, 10); // i = 10;
	Interlocked.Add(ref l, 100); // l = 100;

To replace an existing value, use the Exchange method:

	string name = "Mr. Ed";
	Interlocked.Exchange(ref name, "Barney");

To check if another thread has changed a value out from under the existing code before replacing the existing value, use the CompareExchange method:

	int i = 0;
	double runningTotal = 0.0;
	double startingTotal = 0.0;
	double calc = 0.0;
	for (i = 0; i < 10; i++)
	{
	    do
	    {
	        // Store of the original total
	        startingTotal = runningTotal;

	        // Do an intense calculation.
	        calc = runningTotal + i * Math.PI * 2 / Math.PI;
	    }
	    // Check to make sure runningTotal wasn't modified
	    // and replace it with calc if not. If it was,
	    // run through the loop until we get it current.
	    while (startingTotal !=
	        Interlocked.CompareExchange(
	            ref runningTotal, calc, startingTotal));
	}

Discussion

In an operating system like Microsoft Windows, with its ability to perform preemptive multitasking, certain considerations must be given to data integrity when working with multiple threads. There are many synchronization primitives to help secure sections of code, as well as signal when data is available to be modified. To this list is added the capability to perform operations that are guaranteed to be atomic in nature.

If there has not been much threading or assembly language in your past, you might wonder what the big deal is and why you need these atomic functions at all. The basic reason is that the line of code written in C# ultimately has to be translated down to a machine instruction and along the way the one line of code written in C# can turn into multiple instructions for the machine to execute. If the machine has to execute multiple instructions to perform a task and the operating system allows for preemption, it is possible that these instructions may not be executed as a block. They could be interrupted by other code that modifies the value being changed by the original line of C# code in the middle of the C# code being executed. As you can imagine, this could lead to some pretty spectacular errors, or it might just round off the lottery number that keeps a certain C# programmer from winning the big one.

Threading is a powerful tool, but like most "power" tools, you have to understand its operation to use it effectively and safely. Threading bugs are notorious for being some of the most difficult to debug, as the runtime behavior is not constant. Trying to reproduce them can be a nightmare. Recognizing that working in a multithreaded environment imposes a certain amount of forethought about protecting data access and understanding when to use the Interlocked class will go a long way toward preventing long frustrating evenings with the debugger.

See Also

See the "Interlocked" and "Interlocked Class" topics in the MSDN documentation.


Previous Page
Next Page