Robin's Blog

记录 积累 学习 成长

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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.

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

This code is similar to:

Collapse
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:

Collapse
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()).

Collapse
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).

    Collapse
    //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).

    Collapse
    //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


Member

Occupation: Architect
Company: Synechron
Location: United States United States
posted on 2009-07-30 17:33  Robin99  阅读(237)  评论(0编辑  收藏  举报