Google
WWW Yariv Hammer's Code Site

Friday, March 03, 2006

Synchronizing Threads in .NET

In this serie of articles I show a lot of the mechanisms we have in .NET to program MultiThreaded applications and to perform asynchronous operations.
Here are the articles in this serie:
Asynchronous Programming in .NET
Synchronizing Threads in .NET
MultiThreading Without The Thread Class

Introduction
In my previous article I showed how to start and control a thread. In this article I will show how to synchronize threads.

Thread Local Storage
Lets look at the following example:
---------------------------------------
class A
{
public void f1()
{
Delay = 300;
for (int i=0; i<10; i++)
{
Console.WriteLine("f1: " + Delay);
Thread.Sleep(Delay);
}
}
public void f2()
{
Delay = 500;
for (int i=0; i<10; i++)
{
Console.WriteLine("f2: " + Delay);
Thread.Sleep(Delay);
}
}
public static int Delay = 500;
}
class Class1
{
static void Main(string[] args)
{
A a = new A();
Thread t1 = new Thread(new ThreadStart(a.f1));
Thread t2 = new Thread(new ThreadStart(a.f2));
t1.Start();
t2.Start();
Thread.Sleep(1000);
Console.WriteLine("Main End");
}
}
-------------------------------------------------
Delay is a static member of A. If you will run this you will see that the last thread to change Delay will change it both for f1 and f2. The thing is that both threads share the same member, and the last one to change it wins it all.

What we want really is to have one Delay for the first thread, and another Delay for the second thread. There is a built-in mechanism for that in Win32 called Thread Local Storage, and the way to do it in .NET is by using the [ThreadStatic] attribute before declaring the static member.
[ThreadStatic] public static int Delay = 500;

As you can see we initialized the Delay member with a value. This initialization only applies to the main thread. Each thread must set a value in the entry point method, or the value will be 0.
If you run the program now you will see that each thread has its own delay.

Synchronization Mechanisms
Two threads want to access the same variable, or the same code. Context switches can occur at any time, so if the context switch occur in a critical point, the threads can cause unexpected results. An example could be when a thread tries to decrease a variable by one, so it places it in a register, and at that point a context switch occur, another thread decreases the variable, if a context switch occurs again the variable will be decreased only once instead of twich.

There are declerative ways and coding ways to perform synchronization in .NET.
The WaitHandle class is an abstract class which provides a way to signal other threads. Three classes derive from the WaitHandle class: ManualResetEvent, AutoResetEvent and Mutex.
The AutoResetEvent class involves cases in which we want to make sure that threads will sleep until another thread finishes a task. It has two states: Set and Reset. The Set state is signalling one thread to stop waiting, and then automatically goes back to Reset state. A thread can call the WaitOne method of the AutoResetEvent object, and then it is blocked until someone calls the Set method.
Here is an example.

The ManualResetEvent is similar to AutoResetEvent. When a threads wants to block all other threads it calls the Reset method. If any other thread calls the WaitOne it will be blocked until someone calls the Set method. All the threads will be released, and the state will remain Set, until someone sets the state again to Reset.

The Mutex class is used when we want to protect a resource or a critical code. We call the WaitOne at the beginning of the section, and the ReleaseMutex at the end of the section. If one thread called the WaitOne method, other threads will be blocked on that line until the thread calls the ReleaseMutex class. You should perform the ReleaseMutex operation in the finally clause. For a short and nice example look at MSDN .
In 2005 a Semaphore class was added to the framework. There are also named mutexes, and there is an Access Control List feature.

Race Conditions
The class Interlocked provides methods to perform operations as atomic. This will prevent race conditions between threads on variables. In the 2003 version this class provides Increment, Decrement,Exchange (switches two variables), and CompareExchange. In the 2005 version other features were added.

Using Attributes to Synchronize Classes
A class that derives from ContextBoundObject (such as ServicedComponent) can be assigned with the [Synchronization] attribute. No more than one thread will be able to access the class methods and properties. This will not block static methods and members. This is a very easy but strict mechanism. It is not suitable in many cases, for example when there is a Read and Write method. Only one thread can access the Write method, but few threads can access the Read method. This will not work with this attribute.
The class is in the System.Runtime.Remoting.Contexts namespace.

There is a way to protect a method from getting accessed bymore than one thread. Add the following attribute to the method [MethodImpl(MethodImplOptions.Synchronized)].

Locks
C# has a built-in locking mechanism. The lock is per object, so if you want to prevent two threads from executing critical sections together, they should lock on the same object:
---------------------------------------------

public void f1()
{
lock(x)
{
for (int i=0; i<10; i++)
{
Console.WriteLine("f1: " + i);
Thread.Sleep(300);
}
}
}
public void f2()
{
lock(x)
{
for (int i=0; i<10; i++)
{
Console.WriteLine("f2: " + i);
Thread.Sleep(500);
}
}
}
object x = new object();
--------------------------------------------
Only one method can perform the loop at the same time. The ither thread will have to wait until the lock is freed. You can lock on any type of object.

Behind the scenes .NET uses the class Monitor.
If you want to lock a critical section in the context of the object, you can do lock(this).
This will prevent two threads from accessing the critical section only when accessing the same object.

If you want to lock a section in a static method, you might use lock(typeof(MyClass)) .

A nice class to perform read and write operations is the ReaderWriterLock, which lets aquiring a number of reader locks, and only one writer lock.

Summary
In this article I showed a number of ways to synchronize threads using Locks, Mutex, Manual/AutoResetEvent, and attributes. I also showed how to use the Thread Local Storage.

In the next article I will discuss timers, ThreadPool, and Asynchronous delegates.

0 Comments:

Post a Comment

<< Home

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.