Threads and Synchronization: Part 1

An article related Threads and Thread Synchronization from a Microsoft .NET perspective

Introduction

Today's multi-processing operating system performs multiple operations simultaneously, even if there is only one physical processor in the system. Theoretically it seems impossible, but let's see how multi-processing is achieved with one processor.

There are many stages during the execution of a process where the processor is sitting idle and waiting for some event to occur. For example, if your application is performing some I/O operation, then during I/O operation the processor is sitting idle and waiting for the I/O operation to get completed. Similarly, if your application executes a database query, the processor will be idle and will wait for the database server to respond to the query. Practically speaking, there are numerous stages during the execution of a process when processor is idle. Operating systems utilize this opportunity and employ the idle processor to execute other process. In simple words, the operating system divides the processor time amongst processes to achieve multi-processing.

In the initial days of multi-processing, an operating system used to rely on each process to relinquish the processor regularly to other processes on the system. Thus, in this approach, a poorly designed application or hung application could bring the whole system to a halt. This approach is known as "cooperative multitasking," where each process is supposed to cooperate with the operating system to achieve multiprocessing by surrendering the processor regularly.

The modern operating system follows the "preemptive multitasking" approach, where operating systems have built-in criteria to switch the processor from one process to another and do not depend on underlying processes to relinquish the processor. The act of taking control of the processor from one process and giving it to another process is called "preempting." This approach allows other processes to continue performing, even if one of the processes has hung. The switching of processors is transparent to the processes, so developers do not need to perform any special activity to support multiprocessing operating systems. Criteria for switching the processor from one process to another could be:

Elapsed time: Operating system could divide the processor fixed time amongst processes

Priority: Higher priority process is waiting for the processor

Waiting for an event to occur: Current process is waiting for an I/O and the processor is idle

Mixture of the above

The data structure used by the operating system to schedule and divide the processor time amongst processes is known as a "thread." Each process has at least one thread.

So far, we have discussed the execution of multiple processes. Today's technologies, development framework and languages allow execution of multiple tasks within one process simultaneously, e.g. drawing a graph on-screen and calculating a prime number concurrently by the same process. In other words, one process can have more than one operating system thread to execute multiple tasks concurrently.

The following example demonstrates execution of multiple tasks simultaneously by creating a new thread. The newly created thread is used to execute the function ConcurrentTask().The output window shows the results generated by both the main thread and the newly created thread. Note: Each time you execute the following program, you will have a different output, as you cannot predict at what time the operating system will switch the processor between threads.

From the above example, multithreading/multitasking seems to be quite an easy and interesting subject. On the basis of my experience, I would strongly suggest using as little thread as possible because controlling the execution of multiple threads within an application is really not an easy game. This is especially the case if threads are accessing the shared resources. In this article, I would discuss synchronization issues with multithreading and various techniques and practices available in Microsoft .NET.

.NET Multithreading

The Microsoft .NET Framework provides various ways of achieving multithreading. The simplest approach is to use the Thread class defined in the System.Threading namespace. Although there is no direct mapping between the operating system thread and the managed thread class, for simplicity the Thread class can be considered as a managed wrapper over the operating system thread. Each managed application can have a default thread provided by the CLR.

You can create your own thread by passing a delegate of type System.Threading.ThreadStart. This delegate references a method that will be executed asynchronously by the newly created thread. .NET 2.0 introduces a new constructor of the Thread class that takes a delegate of type System.Threading.ParameterizedThreadStart as a parameter. This delegate, in turn, takes System.Object as a parameter. Thus, with .NET 2.0 and onwards, you can send any object or even a collection of objects to an asynchronous operation, as defined in the following example.

In the above example, factorials of two numbers are calculated asynchronously. The statement thread.Join() is used to make sure that a newly created thread will have finished processing before terminating the application. Within the .NET Framework, a managed thread can either be a foreground or background thread. CLR assumes that a managed application is running if at least one foreground thread is in the running state. If an application does not have any foreground thread running, CLR will shut the application down. By default, threads created by the Thread class are foreground threads. However, you can set them to background by using the IsBackground property.

.NET Thread Pool

To further facilitate the threading model, Microsoft provides a built-in threading pool in .NET. Creating and destroying threads are resource/time-consuming activities. During the creation of a thread, a kernel thread object is created and initialized. Then the thread stack is allocated and notifications are sent to all the DLLs. Similarly, when a thread is destroyed, a kernel object is freed, stack memory is released and notifications are sent to DLLs.

Thus, to increase performance, CLR provides a pool of threads. When an application wants to perform a task asynchronously, it requests the thread pool to execute that task. The thread pool, by using one of its threads, executes the task. When the task is finished, the thread is returned back to the pool. Thus, for each asynchronous request, a new thread is not created. If an application requests more than the available threads in the pool, the thread pool will either put the request into a waiting queue or will create additional threads. If there are many threads sitting idle in the thread pool, the thread pool will destroy idle threads to release system resources. The following code uses the same example of calculating the factorial of two numbers asynchronously by using the thread pool.

Since all thread pool threads are background threads, the main foreground thread sleeps to make sure that the background threads have completed their tasks. Note: You might have a different result from the below.

Background Worker

Microsoft .NET also provides the UI component System.ComponentModel.BackgroundWorker to implement multithreading. This UI component internally uses the thread pool to perform tasks asynchronously. This class raises events to let the host know about the status of asynchronous operation. This class is best suited to keeping your UI responsive, even if you are performing some lengthy operation.

Timer

To perform a task asynchronously on an interval basis, Microsoft .NET provides the Timer class. This class internally uses the thread pool to perform tasks asynchronously.

Asynchronous Delegates

When you define a delegate in your application, CLR adds two more methods: BeginInvoke and EndInvoke. These methods are used to asynchronously call the method referenced by the delegate. BeginInvoke returns IAsyncResult, which is used to monitor the progress of asynchronous operation. BeginInvoke internally uses the thread pool class to invoke the method asynchronously.

To find out more about delegates, please refer my article Study of Delegates. Until now, I have discussed various ways for developing a multithreaded application in .NET. Following this, I'll discuss some thread synchronization solutions available in the Microsoft .NET Framework.

Thread Synchronization

Thread synchronization is required when two or more threads are accessing a shared resource. Accessing of a shared resource by multiple threads at the same time could destabilize an application; e.g. if two threads are writing in the same file, then data in the file would be unpredictable. A shared resource could be a file, memory block, device or collection of objects.

The segment of code where the thread is accessing the shared resource is known as the "critical section." Thread synchronization solutions ensure that if one thread is executing its critical section, no other thread can enter into the critical section. For writing a stable multithreaded application, one should have a clear understanding of thread synchronization and its solutions. It is one of those programming areas which, if not coded well, could be extremely difficult to debug and could also deteriorate the performance of an application. Thread synchronization solutions must satisfy the following conditions:

Mutual exclusion: If a thread is in the critical section, no other thread can enter into the critical section.

Progress: If no thread is executing in its critical section and there exist some other threads that wish to enter their critical section, then the selection of the threads that will enter the critical section next cannot be postponed indefinitely.

The Microsoft .NET Framework provides multiple solutions for thread synchronization. We will go through these solutions one-by-one and discuss how they satisfy the above-mentioned requirements for thread synchronization.

SyncBlock

When an object is created in heap, CLR adds one more field known as SyncBlock. CLR provides a mechanism for granting ownership of the SyncBlock of an object to a thread. At any given time, only one thread can own the SyncBlock for a given object. Threads can request ownership of the SyncBlock for a given object obj by executing the statement Monitor.Enter(obj). If no other thread owns the SyncBlock of object obj, then ownership will be granted to the current thread, else the current thread will be suspended. Monitor.Exit(obj) is used to release the ownership. The following example uses the Monitor class to synchronize the threads, accessing a shared resource, historyData;:

The C# language provides a simple "lock" statement as an alternative to the Monitor class. When following the SyncBlock approach, please keep in mind the following common mistakes:

Do not use value data types for thread synchronization.

SyncBlock is associated with a reference data type. Thus, if you pass value data types as arguments to Monitor.Enter, they will first be boxed and then ownership of the SyncBlock of the boxed value will be granted to the calling thread. Therefore, if two threads try to acquire ownership for a value type, both will get ownership because both are referring to two different boxed values. Similarly, Monitor.Exit will not release the lock because it will be referring to a different boxed value, as shown in the following example:

class HistoryManager
{
privateint threadSync;
private System.Collections.Hashtable historyData;
public HistoryManager()
{
threadSyncObject = newobject();
historyData = new System.Collections.Hashtable();
}
publicObject ReadHistoryRecord(int historyID)
{
try
{
//threadSync will be boxed and then //the ownership of SyncBlock of //newly boxed value will be immidiately//granted to current thread
System.Threading.Monitor.Enter(threadSync);
return historyData[historyID];
}
finally
{
//Will not release the lock as this time it //is a different boxed value
System.Threading.Monitor.Exit(threadSync);
}
}
publicObject AddHistoryRecord(int historyID, Object historyRecord)
{
try
{
//threadSync will be boxed and then the //ownership of SyncBlock of // newly boxed value will be immidiately//granted to current thread
System.Threading.Monitor.Enter(threadSync);
historyData.Add(historyID, historyRecord);
}
finally
{
//Will not release the lock as this time it //is a different boxed value
System.Threading.Monitor.Exit(threadSync);
}
}
}

Do not use the Type class for thread synchronization.

Every type has an associated type descriptor class. You can get the reference of a type descriptor by calling the GetType() method. Since the type descriptor is an object and has an associated SyncBlock, it can be used for thread synchronization. Since there is only one instance per type within a process, you should not use it for thread synchronization, as some unrelated code might have used it for locking purposes and could cause a deadlock. Microsoft recommends using local objects for thread synchronization.

GC collects garbage on its own thread.

If you lock an object and then destroy that object without releasing its lock and if, at the same time, garbage collection starts collecting the object, the finalize method might be called. This is because, when the garbage collector starts collecting the garbage, all other threads are stopped and the finalize method is called by the GC thread. Thus, if you try to lock the object in the finalize method, the GC thread cannot lock it, as it is already locked by the main thread. This will freeze the application. This situation is displayed in the following situation:

SyncBlock is one of the ways of implementing synchronization among threads in the Microsoft .NET Framework. In Part 2 (which is on its way) of this article, I'll discuss other thread synchronization solutions available in the Microsoft .NET Framework.

Is it possible to have an article (part 3?) about threading and WinForm UI, I try to make sure both parts are totally apart so as I have an independant component and the UI, so I can use the component with lenghty operation anywhere (winform, webservice, asp.net). So I need to know the best approach for coding the lenghty operation in the component to make it easy to use.

I am still working on part 2. But due to my heavy busy schedule with client, it would take some time. I'll definetely consider your suggestion and will try to present different approaches for different technologies like ASP.Net, Windows Forms etc.

One thing I have noticed is that using Lock() only locks on the current critical section. If I use Lock() in two different code locations (locking on the same object), both critical sections can be accessed by different threads at the same time.

I would think that CritSection2 would block on LockObj until CritSection1 finishes (assuming CritSection1 started first, and CritSection2 hit the Lock() while CritSection1 is still inside the Lock()). However, with Lock(), it seems that both CritSection1 and CritSection2 can access their critical sections at the same time.

I believe CriticalSection1 and CriticalSection2 can not be executed simultaneously by two different threads if the same object is being used to locking. I wrote a following sample application. Until both messages are printed by any (whoever enters first) critical section, no messages will be printed by the other critical section.

I just looked over what I was trying to do in my app that had the problem. I think I found the problem. The object that I was locking on, it's value was being modified by another class. As I understand it, this changes the lock handle on the object, and Monitor() sees the object as a new object. This would explain why, in my problem app, both functions could execute at the same time.