Table of Contents
Below are the topics grouped by relationship to each other:
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.
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.
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 (M3 and M4 have different set of data shared between methods M3 and M4). 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.
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)
{
}
This code is similar to:
Monitor.Enter(obj);
Monitor.Exit(obj);
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))
{
}
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;
this.objLock = objLock;
Monitor.Enter(objLock);
this.status = Status.Acquired;
}
public void Dispose()
{
Monitor.Exit(objLock);
}
Here is the main screen of the sample application:
Deadlock Diagnostic Information:
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).
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).
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.
- 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:
- 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.
- 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.
- 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.