找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 8282|回复: 7

有什么办法检测死锁阻塞在哪里么?

[复制链接]
发表于 2010-2-21 15:02:58 | 显示全部楼层 |阅读模式
别人写的程序,比较大,10w行代码。ACE跨平台,人已经离职了。
多线程的,问题很多,现在只能改成单线程,但是ui和底层逻辑无法合并到一个线程,所以现在是2个线程在跑。
现在的问题是,频繁崩溃、死锁。很多地方不加锁就崩溃,加锁就死锁。
崩溃还好处理一些,分析dmp文件大部分都能找到出问题的地方。

但是死锁不好调试啊,这么大的程序,那么多锁,逻辑又很缠绵纠结,鬼知道问题出在哪。
好在只有两个线程,而且所有的锁都改成了递归锁。

现在我想知道的是,有没有办法知道,目前某一线程是在wait哪一个锁而block,已经hold住哪些锁么?
windows和linux都需要。
发表于 2010-2-21 15:55:01 | 显示全部楼层
真够你受罪的了。
稍等给你提几个建议,也许能祝你一臂之力。
发表于 2010-2-21 16:06:31 | 显示全部楼层
几个建议:
1、立刻使用工具做检查,查出一个解决一个。因为理解别人的代码,不是朝夕之功能做到的。用工具见效快。
2、紧盯业务数据处理流动,注意代码“环”,从我的经验出发,感觉这是死锁的重要原因。所谓环,就是A->B->C->A。可能C没有直接调用A,但是也许有定时处理器、回调激发了调用。可以用日志打印一下调用过程。
3、参考我的一个小帖子,在某些场合对付死锁,非常有效。
http://www.acejoy.com/bbs/viewth ... &extra=page%3D1
服务器程序开发中的思维转换和注意要点


在日常的服务器程序开发中,我也经历过很多挫折和错误,慢慢的总结出了一些经验和教训,在这里和大家分享和讨论。

1、注意思维问题,不要像开发客户端程序一样的模式思考服务器的处理模式。
在写相关的处理的时候,随时要想着,这段代码可能会被大量用户并发调用,是否会引起同步访问、唯一标识错误等问题。我就犯过一次大错,在标识客户端ID和处理序列的时候,居然只记录了处理序列号,而这个序列号很可能重复,导致一个重大错误。
2、注意同步中的问题。
    绝大多数服务器软件不可避免的会使用多线程的机制,这就引入了一个同步的问题。相信大多数能做服务器端程序的,对线程的同步都很了解,但是这未必不会犯错。同步带来两个问题,第一个就是性能下降,如果能够消除不必要的同步,分离数据,就能有效的消除性能瓶颈。第二个更可怕,死锁。ACE_GUARD系列宏,在你不经意的时候,经常给你带来问题,如果这个宏同时和delete this使用,大多数都会崩溃。而死锁,更是难以调试,让人头大。发生死锁,需要你仔细的分析代码处理的流程和可能发生死锁的位置。这里介绍一个经验,如果你怀疑自己的程序发生死锁,你可以使用Process Explorer,(www.sysinternals.com,被微软买了),安装调试符号,打开你需要检查的程序,查看线程堆栈,就能看到各个线程大概在做什么,是否有同步的acquire()等调用,如果程序不在处理数据,而线程都在acquire(),很可能死锁了。
    分析死锁发生的位置和原因,需要操作系统的相关知识,明白死锁的理论。具体处理呢,就是在代码中寻找“环”,寻找环路的处理依赖。比如A通过B调用了C,而C在不同的线程,又调用了A,A、B、C中的代码,都有同步处理过程,这个就容易发生死锁,想办法打破这种循环依赖关系,不仅仅对减少代码耦合重要,更对服务器的死锁预防重要。
 楼主| 发表于 2010-2-21 18:09:11 | 显示全部楼层
我不入地狱谁入地狱。。。
只有借助工具了,实在没心情去仔细读那些代码。
好的,Process Explorer,研究下。
多谢老大~!
发表于 2012-5-31 17:05:57 | 显示全部楼层
学习下!
发表于 2012-6-1 14:44:36 | 显示全部楼层
没必要这么复杂。
多线程不要刻意去区分服务器还是客户端。

不管是windows和linux下,你用调试工具(vs or gdb)都可以清楚去查锁变量里面的成员,记录了当前这个锁是被哪个线程锁住了。
注意linux下的线程ID 是轻量级ID,不是线程对象

没编过多线程程序 理解多线程是要稍微学习一下相关知识点。
发表于 2014-2-19 11:08:45 | 显示全部楼层
ACE包含一个令牌子系统(Token Library),令牌与一般的锁类似,但该系统具有检测死锁的能力,在编译ACE时需要在config.h文件中定义如下宏来生成该子系统:ACE_HAS_TOKENS_LIBRARY。具体而言代码是这样:
  1. #include "stdafx.h"
  2. #include "ace/Task.h"
  3. #include "ace/Local_Tokens.h"
  4. #include "ace/Token_Manager.h"
  5. //#define IGNORE_DEADLOCK
  6. class task : public ACE_Task_Base
  7. {
  8. public:
  9.     task(char const* name1, char const* name2)
  10.         : name1_(name1)
  11.         , name2_(name2)
  12.     {
  13.     }
  14.     virtual int svc()
  15.     {
  16. #if defined(IGNORE_DEADLOCK)
  17.         ACE_Local_Mutex mutex1(name1_, 1, 1);
  18.         ACE_Local_Mutex mutex2(name2_, 1, 1);
  19. #else
  20.         ACE_Local_Mutex mutex1(name1_, 0, 1);
  21.         ACE_Local_Mutex mutex2(name2_, 0, 1);
  22. #endif
  23.         int result = mutex1.acquire();
  24.         ACE_DEBUG((LM_DEBUG, "(%t) mutex1 acquired %d!\n", result));
  25.         ACE_OS::sleep(2);
  26.         {
  27.             int result = mutex2.acquire();
  28.             ACE_DEBUG((LM_DEBUG, "(%t) mutex2 acquired %d, %u!\n", result, ACE_OS::last_error()));
  29.             if(result == 0)
  30.                 mutex2.release();
  31.         }
  32.         if(result == 0)
  33.             mutex1.release();
  34.         return 0;
  35.     }
  36. private:
  37.     char const* name1_;
  38.     char const* name2_;
  39. };
  40. int ACE_TMAIN(int argc, ACE_TCHAR* argv[])
  41. {
  42.     // must call this to see the dead lock dump.
  43.     ACE_Token_Manager::instance()->debug(1);
  44.     task t1("A", "B");
  45.     task t2("B", "A");
  46.     t1.activate();
  47.     ACE_OS::sleep(1);
  48.     t2.activate();
  49.     t1.wait();
  50.     t2.wait();
  51.     return 0;
  52. }
复制代码

这是一个刻意制造的死锁情况,程序输出如下:

(2844) acquired A
(2844) mutex1 acquired 0!
(3040) acquired B
(3040) mutex1 acquired 0!
(2844) waiting for B, owner is /YUNHAI/1392/3040, total waiters == 2
(3040) Deadlock detected.
/YUNHAI/1392/3040 owns B and is waiting for A.
/YUNHAI/1392/2844 owns A and is waiting for B.
(3040) mutex2 acquired -1, 36!
(3040) released B, owner is /YUNHAI/1392/2844, total waiters == 1
(2844) unblocking /YUNHAI/1392/2844.
(2844) mutex2 acquired 0, 10035!
(2844) released B, owner is no owner, total waiters == 0
(2844) released A, owner is no owner, total waiters == 0

可以看到,死锁情况被检测到了,线程3040占有B但想获取A,线程2844占有A但想获取B,从而形成死锁。这段代码摘自《ACE程序员指南》P232,稍有改动,可以参看原文获取更详细的信息。这里想说的是,如果你将程序中使用的ACE_Thread_Mutex/ACE_Recursive_Thread_Mutex 替换成ACE_Local_Mutex,并启动调试输出,就可以看到死锁时是哪些线程在竞争哪些锁了。ACE_Local_Mutex本身是可重入的递归锁,接口与ACE_Thead_Mutex完全一样,可以做无缝替换,最好定义一个typedef,方便在它们之间进行切换。
但需要注意的是,最大的锁数量与平台支持的TLS/TSS槽个数相关,例如在WIN32上,最多支持64个TLS槽,而Tokens Library的实现依赖于此,所以要确保你的平台可以支持那么多的锁,不然会死的很惨!
发表于 2014-2-19 14:28:28 | 显示全部楼层
    可以使用Token Library写一个经典的哲学家进餐问题,并观察死锁情况,代码如下:
  1. #include "stdafx.h"
  2. #include "ace/Local_Tokens.h"
  3. #include "ace/Token_Manager.h"
  4. #include "ace/Thread_Manager.h"
  5. #include "ace/Atomic_Op.h"
  6. #include "ace/Task.h"
  7. #define MAX_NUM 5
  8. ACE_Atomic_Op<ACE_Thread_Mutex, long> current_sit = 0;
  9. ACE_Local_Mutex* chopsticks[MAX_NUM] = { 0 };
  10. class philosopher : public ACE_Task_Base
  11. {
  12. public:
  13.     virtual int svc()
  14.     {
  15.         int result = 0;
  16.         int sit = current_sit ++;
  17.         int left = sit;
  18.         int right = (sit + 1) % MAX_NUM;
  19.         ACE_DEBUG((LM_DEBUG, "(%t) takes sit %u, left %u, right %u.\n", sit, left, right));
  20.         while(thr_mgr()->testcancel(ACE_Thread::self()) == 0)
  21.         {
  22.             result = chopsticks[left]->acquire();
  23.             if(result == 0)
  24.             {
  25.                 //ACE_OS::sleep(3);
  26.                 ACE_DEBUG((LM_DEBUG, "(%t) takes left chopstick.\n"));
  27.                 result = chopsticks[right]->acquire();
  28.                 if(result == 0)
  29.                 {
  30.                     ACE_DEBUG((LM_DEBUG, "(%t) takes right chopstick, start eating.\n"));
  31.                     //ACE_OS::sleep(1);
  32.                     chopsticks[right]->release();
  33.                 }
  34.                 chopsticks[left]->release();
  35.                 ACE_DEBUG((LM_DEBUG, "(%t) release both chopsticks.\n"));
  36.             }
  37.         }
  38.         return 0;
  39.     }
  40. };
  41. int ACE_TMAIN(int argc, ACE_TCHAR* argv[])
  42. {
  43.     philosopher p;
  44.     char name[4] = { 0 };
  45.     ACE_Token_Manager::instance()->debug(1);
  46.     for(int i=0; i<MAX_NUM; ++ i)
  47.     {
  48.         ACE_OS::sprintf(name, "%c", 'A'+i);
  49.         chopsticks[i] = new ACE_Local_Mutex(name, 0, 0);
  50.     }
  51.     p.activate(THR_NEW_LWP | THR_JOINABLE, MAX_NUM);
  52.     ACE_OS::sleep(10);
  53.     ACE_Thread_Manager::instance()->cancel_all();
  54.     p.wait();
  55.     for(int i=0; i<MAX_NUM; ++ i)
  56.     {
  57.         delete chopsticks[i];
  58.     }
  59.         return 0;
  60. }
复制代码

    输出比较多,摘一段有死锁的情景列在下面:
  1. (5972) release both chopsticks.
  2. (7856) takes right chopstick, start eating.
  3. (7856) release both chopsticks.
  4. (5972) takes left chopstick.
  5. (6468) takes right chopstick, start eating.
  6. (6468) release both chopsticks.
  7. (4668) takes right chopstick, start eating.
  8. (4668) release both chopsticks.
  9. (4668) takes left chopstick.
  10. (6468) takes left chopstick.
  11. (8060) takes left chopstick.
  12. (7856) takes left chopstick.
  13. (7856) Deadlock detected.
  14. /YUNHAI/1348/7856 owns A and is waiting for B.
  15. /YUNHAI/1348/6468 owns E and is waiting for A.
  16. /YUNHAI/1348/4668 owns D and is waiting for E.
  17. /YUNHAI/1348/8060 owns C and is waiting for D.
  18. /YUNHAI/1348/5972 owns B and is waiting for C.
  19. (7856) release both chopsticks.
  20. (6468) takes right chopstick, start eating.
  21. (6468) release both chopsticks.
  22. (4668) takes right chopstick, start eating.
  23. (7856) takes left chopstick.
  24. (4668) release both chopsticks.
  25. (6468) takes left chopstick.
复制代码

    个人的经验是MAX_NUM越小越容易出现死锁
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-4-29 10:50 , Processed in 0.015192 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表