Click here to Skip to main content
Click here to Skip to main content

Multi-Threading: Deadlock Tracer Utility

By , 3 Nov 2008
 
Prize winner in Competition "Best C# article of November 2008"

Table of Contents

Below are the topics grouped by relationship to each other:

Introduction

In a multi-threaded application, for synchronization, the common coding format followed is ‘lock’. One of the major problems that multi-threaded applications suffer is ‘deadlock’ due to wrong implementation of lock statements. It’s a very difficult and tedious job to find out exactly where a dead lock happens. Although there are certain third party utilities, or you can take a dump and analyze the dead-lock problem, but unfortunately, it’s not a simple task, and moreover, it may happen that your application is in production and a dead-lock occurs causing the UI/activity to freeze, and the dump might not be taken at the user site.

This article explains how you can get proper diagnostic information in dead-lock scenarios so that programmers can fix such issues quickly and efficiently.

What you will get from this article?

1. Identify deadlocks in threads

Inline code/ideas that can detect dead-lock scenarios because of improper implementation of ‘lock’s, and can provide helpful diagnostic information to trace deadlocks.

2. Performance

Also, it can trace a ‘lock’ activity. If multiple threads try to acquire a lock on the same object, only one thread at a time can acquire it, and all the other threads will be in a waiting state. This utility can identify the time for which a thread has been in waiting state for acquiring a lock on an object and the active state time (i.e., acquired the lock and is performing the activity). This analysis is useful if the wrong objects are locked for synchronization, which is hurting the performance of your application.

Example: Suppose the code contains four methods, M1(), M2(), M3(), and M4(). Each of these methods put a lock on the same object, say ‘obj’. But for synchronization, M1 and M2 needs to be synchronized (they share some common data), and M3 and M4 needs to be synchronized (they share common data). But since the same object is used for the synchronization of ‘obj’, if one thread is executing M1() and another tries to execute M3(), that second thread will be in a waiting state until the lock in M1 is released, but as per the code, there is no need for synchronization of M1 and M3, and hence in this scenario, the lock that is put is not correct, it’s hurting the performance of the application.

How thread lock activity is traced?

Here is a general ‘lock’ statement, in which a thread acquires a lock on object ‘obj’, and once the scope of the ‘lock’ ends, it releases the lock on that object.

lock(obj)
{
  // perform activity 1
  // perform activity 2
  // perform activity 3
}

This code is similar to:

Monitor.Enter(obj); //acquire lock on object
// perform activity 1
// perform activity 2
// perform activity 3
Monitor.Exit(obj); //release lock

With Monitor.Enter and Monitor.Exit, we acquire and release the lock on object 'obj', but it has one problem. What will happen if exception occurs in 'activity 1/2/3' and is not handled? In that case, Monitor.Exit will not execute, resulting in 'obj' in locked state only, which is a big problem. In the case of the 'lock' construct, upon exit of 'lock', the lock on the corresponding object is released. So, one solution can be to put Monitor.Exit in finally. But then, it's not a user friendly pattern compared to lock construct. So, the perfect solution for this is the 'using' construct. Upon exit of the ‘using’ statement, the Dispose() method gets called. So, we will put Monitor.Exit in the Dispose method. We can have code like this:

using(ThreadLock.Lock(obj)) 
{
   // perform activity 1
   // perform activity 2
   // perform activity 3
}

Here, ‘ThreadLock’ is a class that implements the IDisposable interface and ‘Lock’ is a static method that returns a new instance of ThreadLock. Upon exit of the ‘using’ statement, the Dispose() method gets called, where we remove the lock on the object (Monitor.Exit()).

public static ThreadLock Lock(object objLock)
{         
    return new ThreadLock(objLock);         
}

public ThreadLock(object objLock)
{
    this.status = Status.Acquiring; //useful for detecting dead-lock
    this.objLock = objLock; 
    //collect useful information about the context such 
    //as stacktrace, time to acquire the lock(T1)
     Monitor.Enter(objLock); 
    this.status = Status.Acquired; 
    //lock is acuired, so collect acquired-time(T2)
    //[T2-T1 = time taken to acquire lock]
}

public void Dispose()
{
     Monitor.Exit(objLock);
    //T3: activity in a lock is over
    //Serialize this class for doing analysis of thread-lock activity time 
}

About the sample

Here is the main screen of the sample application:

MainScreen.JPG

1. Deadlock section

  • Click the Generate Thread ‘Dead-Lock’ button. It generates a dead-lock between the threads.
  • Wait for around 3-5 seconds and then click the Scan ‘Dead-Lock’ button. It processes the thread locking information that is stored in the collection, and from that information, detects if a deadlock is there.
  • If a dead-lock is found, it shows the details as shown:
  • Deadlock Diagnostic Information:

    DeadLockStackTrace.JPG

    PerformanceTestScreen.JPG

    Click the Regular ‘lock’ test button. It performs the following code for the number of iterations specified in the textbox. In this case, it is 100000 (hundred thousand).

    //Execute following line 100000 (hundred thousand) times
    lock (objLockTest)
    {
    }

    Click the Using ‘ThreadLockTracer’ Utility button. it performs following code for the number of iterations specified in the textbox. In this case, it is 100000 (hundred thousand).

    //Execute following line 100000 (hundred thousand) times
    using (ThreadLock.Lock(objLockTest))
    {
    }

    From the results, it is clear that the ‘Thread Lock Tracer’ utility is highly efficient; for hundred thousand iterations, it takes hardly 100 ms time extra, which should be acceptable, but in return, in case of trouble, it provides exclusive diagnostic information that is very much useful for fixing the problem.

2. Tracing the thread activity section

  • Click on Generate Thread Activity 1 and Generate Thread Activity 2 to have some regular threading lock activity. After 5-8 seconds, click on Scan Thread Activity. It shows the threading-lock activity details as shown.
  • From this graph, the faint colors indicate that the thread is waiting for acquiring a lock, and the dark colors (blue/red) indicate that the thread has acquired a lock for that much time duration.
  • Also, on click of any horizontal graph line, it will highlight (in blue color) the records of the same object.
  • ThreadActivity.JPG

Note

  • Using the concept explained in this article, tracing Thread-Deadlock functionality is highly efficient (for hundred thousand iterations, it takes around 100 ms only, this time may vary on your machine), and you can have it in your code. For this, we need to just search and replace the calls to ‘lock’ statements, and hence should not be too much of work.
  • Identifying Thread-Deadlock can be performed in a separate thread or at the application exit.
  • ‘Tracing Thread Activity’ functionality is supposed to be used only in development environments. Tracing this information is costly in terms of performance, and hence it is suggested to switch it off in production scenarios.

Conclusion

  • Tracing deadlock in threads caused due to improper ‘lock’ usage is very easy and simple, and makes the life of developers easy.
  • The ‘Tracing Thread Activity’ functionality is useful in development environments to find out if an incorrect lock exits that is hurting the performance of a multi-threaded application.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Chivate Atul
Architect Synechron
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralSome suggested impovementsmemberMember 308679413 May '09 - 1:13 
First of all, really nice idea Big Grin | :-D
 
I develop real time application with lot of locking, so when I discover your class I use it.
Follow some improvement I include in my version.
 
I found just one error in ctrlThreadActivity.DeSerializeThreadLock
You must replace :
fs = new FileStream(ThreadLock.threadActivityTracingLocation + i.ToString() + ".txt", FileMode.Open);
by :
fs = new FileStream(files[i], FileMode.Open);
 
I add Thread Name and Lock Name informations.
 
So, I add new class LockerObject with name property.
	public class LockerObject
	{
		#region "Constructeurs /Destructeur"
		private LockerObject()
		{
		}
		public LockerObject(string Name)
		{
			mName = Name;
		}
		#endregion
		#region "Proprietes et variables membres associees"
		private string mName;
		public string Name
		{
			get
			{
				return mName;
			}
		}
		#endregion
	}
 
 

Add property in ThreadLock
        public int id;
        public int ThreadId;
        public bool IsBackgroundThread;
<code>	public string ThreadName;
	public string LockName;
 
Fill this property in ThreadLock constructor :
	...
	this.IsBackgroundThread = Thread.CurrentThread.IsBackground;
<code>	this.ThreadName = Thread.CurrentThread.Name;
	if (objLock is LockerObject) this.LockName = ((LockerObject) objLock).Name;
 
Add this information in sbStackInfo in both label1_MouseMove and label2_MouseMove
so replace :
sbStackInfo.Append(Environment.NewLine + "ThreadId: " + objLockInfo.ThreadId.ToString() + " IsBackground:" + objLockInfo.IsBackgroundThread.ToString());
by
	sbStackInfo.Append(Environment.NewLine + "Thread Id: " + objLockInfo.ThreadId.ToString());
	if (objLockInfo.ThreadName != null) { sbStackInfo.Append(", Name: " + objLockInfo.ThreadName); }
	sbStackInfo.Append(", IsBackground: " + objLockInfo.IsBackgroundThread.ToString());
 
	if (objLockInfo.LockName != null) sbStackInfo.Append(Environment.NewLine + "Locker Name: " + objLockInfo.LockName);
 
Hope this help Shucks | :->
 
Regards
GeneralRe: Some suggested impovementsmemberChivate Atul28 May '09 - 23:45 
Hi,
 
I am glad that this article helped your project. Since this is just a POC kind of application I haven't stressed on all those points, but anyway thanx for pointing out improvements.
 
-Atul
GeneralThanksmemberBrij20 Dec '08 - 0:38 
Really great stuff.The same I was looking for? Thanks again
 
Cheers!!
Brij

GeneralDumpingmemberDominic Crathern19 Dec '08 - 2:14 
'or you can take a dump and analyze the dead-lock problem' - I always find that helps to focus the mind Smile | :) . Nice article
GeneralRe: DumpingmemberChivate Atul19 Dec '08 - 5:05 
Yes, sure till mostly we depend on dump as I have explained in the 'Introduction' section. Even I was also used to analyze the dump. But problem with dump approach is it have a lot of information, and you need to analyze it carefully. Whereas with this utility it can give the stack trace info of the threads that are part of dead-lock only, very specific information that can be easily analyzed by any developer.
 
Also it provides additional information regarding inefficient usage of 'lock' if any.
 
If you have any query, please free to ask Smile | :)
 
Thanx
Generallock's meaningmemberhomeropata18 Dec '08 - 21:08 
The lock statement is not exactly means that you wrote.
It is similar to this:
Monitor.Enter(obj); //acquire lock on object
try {
  // perform activity 1
  // perform activity 2
  // perform activity 3
} finally {
  Monitor.Exit(obj); //release lock
}
Check this![^]
(Sorry for my english, but it is not very good)
GeneralRe: lock's meaningmemberChivate Atul19 Dec '08 - 4:57 
Yes, as a first step towards concept explaination, I put as plain Monitor.Enter & Monitor.Exit()....
Ultimately when scope of 'lock' ends, it releases lock on that object. So lock release operation can be performed in 'finally' as you have explained, but putting it in finally makes porting exiting code little difficult. Instead 'using' statement does exactly the same thing with almost similar code, so replacing existing code 'lock' statements with 'using' approach as explained in this article is very simple operation, and that's why I preferred 'using' approach.
 
This is already explained in the article Smile | :)
>>>>>(text from article)
 
With Monitor.Enter and Monitor.Exit, we acquire and release the lock on object 'obj', but it has one problem. What will happen if exception occurs in 'activity 1/2/3' and is not handled? In that case, Monitor.Exit will not execute, resulting in 'obj' in locked state only, which is a big problem. In the case of the 'lock' construct, upon exit of 'lock', the lock on the corresponding object is released. So, one solution can be to put Monitor.Exit in finally. But then, it's not a user friendly pattern compared to lock construct. So, the perfect solution for this is the 'using' construct. Upon exit of the ‘using’ statement, the Dispose() method gets called. So, we will put Monitor.Exit in the Dispose method.
 
>>>>
 
If you have further queries please free to ask me Smile | :)
 
Thanx
GeneralGr8 workmemberMs SAARA17 Dec '08 - 5:31 
helpful article
GeneralRe: Gr8 workmemberChivate Atul19 Dec '08 - 5:14 
thanx Sara for your comments
GeneralHelpful Articalmemberphilmicholson15 Dec '08 - 11:33 
Helped me in my project.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 3 Nov 2008
Article Copyright 2008 by Chivate Atul
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid