一个有趣的.net程序死锁问题
最近遇到一个有趣的.net 2.0程序死锁问题,一般来说.net死锁问题都是应用程序显示的请求锁的过程出现锁访问顺序不一致导致的,但是本文中这个死锁则相对较为隐晦,隐藏的很深。
调试过程
.net的死锁我们可以通过sos.dll提供的syncblk来查看sync block来发现那些线程拥有锁,哪些线程等待锁。所以我们先通过syncblk来查看以下输出如何。
通过syncblk可以看到目前有一个syncblk已经被线程3(系统线程1814)所拥有。
0:005> .loadby sos mscorwks 0:005> !syncblk Index SyncBlock MonitorHeld Recursion Owning Thread Info SyncBlock Owner 3 0000000000f4e678 3 1 0000000000f58320 1814 3 0000000002ef6040 System.Object ----------------------------- Total 3 CCW 0 RCW 0 ComClassFactory 0 Free 0
切换到3号线程,验证是否系统线程号为1814。输出其调用栈。原来该线程在等待进入另外一个CriticalSection。
0:005> ~3s ntdll!ZwWaitForSingleObject+0xa: 000007f8`04ba2c2a c3 ret 0:003> ~. . 3 Id: 88c.1814 Suspend: 1 Teb: 000007f5`ff396000 Unfrozen Start: mscorwks!Thread::intermediateThreadProc (000007ff`e7d5f33c) Priority: 0 Priority class: 32 Affinity: f 0:003> kvL Args to Child : Call Site 00000000`00f535d0 00000000`000001f8 00000000`00000000 00000000`00f07c70 : ntdll!ZwWaitForSingleObject+0xa 00000000`00000000 000007ff`43010042 00000000`00f535d0 00000000`00000001 : ntdll!RtlpWaitOnCriticalSection+0xea 00000000`0000000a 00000000`00000000 00000000`00f1c5b0 00000000`00f535d0 : ntdll!RtlpEnterCriticalSectionContended+0x94 00000000`00000a45 00000000`00000000 ffffffff`fffffffe 000007ff`88533480 : mscorwks!UnsafeEEEnterCriticalSection+0x20 00000000`00000000 000007ff`e82758f0 ffffffff`fffffffe 00000000`00000000 : mscorwks!CrstBase::Enter+0x123 ffffffff`00000001 00000000`00f4d920 00000000`00000000 00000000`00000001 : mscorwks!ListLockEntry::FinishDeadlockAwareEnter+0x2b 00000000`1b81e100 00000000`00f48e80 00000000`00000000 00000000`00000000 : mscorwks!ListLockEntry::LockHolder::DeadlockAwareAcquire+0x32 000007f8`01eb798a 000007ff`883f3ba0 00000000`00000003 000007ff`883f3958 : mscorwks!MethodTable::DoRunClassInitThrowing+0x6cb 00000000`00000000 00000000`00000000 ffffffff`fffffffe 000007ff`e1261560 : mscorwks!MethodTable::CheckRunClassInitThrowing+0x68 00000000`02ef8148 00000000`00f0c040 00000000`00000000 000007ff`883f3b90 : mscorwks!MethodDesc::DoPrestub+0x162 00000000`02ef4e40 00000000`00000000 00000000`02ef5fe8 00000000`00f58320 : mscorwks!PreStubWorker+0x1fa 00000000`1b81ec50 00000000`00000000 00000000`1b81ea50 00000000`00000000 : mscorwks!ThePreStubAMD64+0x87 00000000`02ef6028 000007ff`e16334c0 00000000`1b81ed00 00000000`00000000 : StaticConstruction!StaticConstruction.Singleton.LockIt()+0x10c 00000000`02ef4c20 000007ff`e16334c0 00000000`1b81ed00 00000000`00000000 : StaticConstruction!StaticConstruction.Program.Thread1Proc()+0x37
...
00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x1d
将该CriticalSection输出,查看一下拥有的线程是1994,即4号线程。
0:003> !cs 00f535d0 ----------------------------------------- Critical section = 0x0000000000f535d0 (+0xF535D0) DebugInfo = 0x0000000000f402e0 LOCKED LockCount = 0x1 WaiterWoken = No OwningThread = 0x0000000000001994 RecursionCount = 0x1 LockSemaphore = 0x1F8 SpinCount = 0x00000000020007d0 0:003> ~ 0 Id: 88c.1f7c Suspend: 1 Teb: 000007f5`ff39e000 Unfrozen 1 Id: 88c.18a4 Suspend: 1 Teb: 000007f5`ff39c000 Unfrozen 2 Id: 88c.1438 Suspend: 1 Teb: 000007f5`ff39a000 Unfrozen . 3 Id: 88c.1814 Suspend: 1 Teb: 000007f5`ff396000 Unfrozen 4 Id: 88c.1994 Suspend: 1 Teb: 000007f5`ff394000 Unfrozen # 5 Id: 88c.174c Suspend: 1 Teb: 000007f5`ff1be000 Unfrozen
切换到4号线程查看调用栈,可以看到该调用栈正在等待StaticConstruction.Singleton.LockIt中的Monitor.Enter,即三号线程拥有的syncblk。
0:003> ~4s ntdll!ZwWaitForMultipleObjects+0xa: 000007f8`04ba319b c3 ret 0:004> kL Child-SP RetAddr Call Site 00000000`1b91d348 000007f8`01e812d2 ntdll!ZwWaitForMultipleObjects+0xa 00000000`1b91d350 000007ff`e7c3e809 KERNELBASE!WaitForMultipleObjectsEx+0xe5 00000000`1b91d630 000007ff`e7c431f1 mscorwks!WaitForMultipleObjectsEx_SO_TOLERANT+0xc1 00000000`1b91d6d0 000007ff`e7d403e5 mscorwks!Thread::DoAppropriateAptStateWait+0x41 00000000`1b91d730 000007ff`e7c5e95c mscorwks!Thread::DoAppropriateWaitWorker+0x191 00000000`1b91d830 000007ff`e7c9d17a mscorwks!Thread::DoAppropriateWait+0x5c 00000000`1b91d8a0 000007ff`e7c20fe1 mscorwks!CLREvent::WaitEx+0xbe 00000000`1b91d950 000007ff`e7d6e012 mscorwks!AwareLock::EnterEpilog+0xc9 00000000`1b91da20 000007ff`e817a825 mscorwks!AwareLock::Enter+0x72 00000000`1b91da50 000007ff`88540657 mscorwks!JIT_MonEnterWorker_Portable+0xf5 00000000`1b91dc20 000007ff`885404c3 StaticConstruction!StaticConstruction.Singleton.LockIt()+0xa7 00000000`1b91dcc0 000007ff`e7ddd562 StaticConstruction!StaticConstruction.Static..cctor()+0x93 00000000`1b91dd30 000007ff`e7d1a293 mscorwks!CallDescrWorker+0x82 00000000`1b91dd70 000007ff`e7d1a3da mscorwks!CallDescrWorkerWithHandler+0xd3 00000000`1b91de10 000007ff`e7cfd437 mscorwks!DispatchCallDebuggerWrapper+0x3e 00000000`1b91de70 000007ff`e7cf22bd mscorwks!MethodTable::RunClassInitEx+0x207 00000000`1b91dfc0 000007ff`e8165f98 mscorwks!MethodTable::DoRunClassInitThrowing+0x74d 00000000`1b91ea40 000007ff`e814f162 mscorwks!MethodTable::CheckRunClassInitThrowing+0x68 00000000`1b91ea80 000007ff`e7cf72aa mscorwks!MethodDesc::DoPrestub+0x162 00000000`1b91eb70 000007ff`e7ddd447 mscorwks!PreStubWorker+0x1fa 00000000`1b91ec30 000007ff`88540300 mscorwks!ThePreStubAMD64+0x87 00000000`1b91ed00 000007ff`e14e2bdb StaticConstruction!StaticConstruction.Program.Thread2Proc()+0x20 ... 00000000`1b91f8c0 00000000`00000000 ntdll!RtlUserThreadStart+0x1d
因此我们可以看到是3号线程和4号线程互相得到了彼此需要请求的锁,因而造成了死锁。
其中4号线程很容易理解,他在等待monitor.enter,但是如何解释3号线程的调用栈,3号线程同样是调用StaticConstruction.Singleton.LockIt,其等待的CritcalSection来自何处又是做什么用的呢?
回头查看程序代码,
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace StaticConstruction { class Program { static void Main(string[] args) { Thread t1 = new Thread(Thread1Proc); Thread t2 = new Thread(Thread2Proc); t1.Start(); // lockA -> wait for static construction t2.Start(); // static consctruction -> lockA Console.Read(); } static void Thread1Proc() { Singleton.Instance.LockIt(); } static void Thread2Proc() { Static.Foo(); } } class Singleton { private object lockA = new object(); private Singleton() { } private static Singleton _instance = new Singleton(); public static Singleton Instance { get { return _instance; } } public void LockIt() { Console.WriteLine("Thread {0} waiting lock A", Thread.CurrentThread.ManagedThreadId); lock (lockA) { Console.WriteLine("Thread {0} got lock A", Thread.CurrentThread.ManagedThreadId); Thread.Sleep(10); Static.Foo(); } Console.WriteLine("Thread {0} released lock A", Thread.CurrentThread.ManagedThreadId); } } class Static { private Static() { } static Static() { Console.WriteLine("Static constructor begin by thread {0}", Thread.CurrentThread.ManagedThreadId); Singleton.Instance.LockIt(); Console.WriteLine("Static constructor end by thread {0}", Thread.CurrentThread.ManagedThreadId); } public static void Foo() { Console.WriteLine("Static method Foo begin"); Console.WriteLine("Static method Foo end"); } } }
原来3号线程在StaticConstruction.Singleton.LockIt得到锁之后紧接着要调用一个静态类Static的方法,而回到4号线程的调用栈可以看到该静态类Static构造函数还在执行过程中,在这个静态类被构造成功之前,3号线程当然无法调用该类上的方法,原来CLR通过在methodtable初始化过程中通过一个CriticalSection来确保线程安全。因此也使得在这种特殊情况下会有死锁情况发生。
明白了这一点,解决方法就很简单了,将锁的请求顺序统一,只要大家都按照同样的请求顺序来请求锁,死锁问题就不会发生了。
Java程序员你凭什么比.NET工资高?
难道博客园沦落了?开始语言之争了?很久没看到这样的话题了,看到楼主的抱怨,本来想喷一下的,看了下面的评论,还是不喷了。在没有对一件事物完全了解之前,不要片面的发表自己的“高见”。
声明:本文无对原文楼主和评论同学的不敬!只作为解释和探讨。
我是java语言使用者,三年多的经验,同时可以说我对.NET一窍不通,发此文的时候我也觉得自己厚颜无耻,下面我就说说我对java阵营的认识吧:
一、特性
java有着其他语言不能比拟的开源优势,有着linux系统良好的支撑,所以我们就看到了很多基于java和linux的优秀开源/非开源软件,社区的繁荣代表着这个语言的流行度以及使用的普及度。
框架:我们并不是每天在框架上“瞎折腾”,框架只是在某一方面封装的通用组件,你可以使用,也可以写出更好的框架满足“特定”项目的需求,当然也可以不写框架,这个不是硬性要求,业务第一,有满足业务的优秀框架,为什么不用呢?不过老手都知道,代码写的多了,会不自然的想要将通用部分封装,改良后纳入自己的代码库,以便以后加以利用,缩短项目周期,虽说业务千变万化,但是还是有很多相同之处的,像园子里很多同学技术文章就是框架的雏形了。在项目架构过程中,有那么多开源组织,开源社区的优秀解决方案可供参考和选择,既解决了问题又让自己在技术和架构层面有了新的认识和收获,一举两得的美事,不知道怎么就变成了“瞎折腾”了呢?java可不是只有SSH而已哦!
IDE:java的IDE是很多,这就让同学们在做项目的时候多了一些选择,一般开发可以用Eclipse,NetBeans,MyEclipse,根据IDE的特点和自己的爱好选择就是了,本人除了开始学java时候用的是Myeclipse,后来做手机软件用Eclipse,接触java的三年多一直用Eclipse,因为用的多了,jdk,tomcat,apache这些IDE需要配置的项目也是手到擒来,根本不像有些人说的那样一大堆配置,配置是根据项目来的,基本的配置没有有些同学说的那么夸张,那些“可配置项”不是必须配置的,用到的功能就配置,用不到就放在那。
Web服务器:其实不仅是在web服务器上面“瞎折腾”,我们还在数据库、缓存、邮件服务器等众多的服务器上面“瞎折腾”,这种折腾是为了选择出符合项目需求、与团队技术水平和项目技术架构相契合的最佳配置,所谓“工欲善其事,必先利其器”;所以我们才要在Apache和Nginx上测试,比较他们在资源消耗,吞吐量,稳定性上面的差异;比较Redis和Memcache,两种缓存的底层算法,存储数据类型,资源消耗……
另外说一句:很多项目到了一定程度,在并发、数据量和安全性上面有非常高的要求,需要实现“定制化”,java 的很多配置和Linux配置都是为了实现这种定制化,详细到java组件或者系统下面的一个参数,这种“可定制化”,在大型的项目中是必不可少的,对于解决问题和性能优化大有裨益。可定制化的语言,可定制化的服务器,可定制化的数据库,做梦都会笑醒。
二、环境
表面上看,在相同工作经验,相同学历和相同工作时间下,java程序员工资比.NET高,我们都知道看事情不能片面的看,要辩证的看,那么我们还要看另一个数据,那就是这个语言的从业人数,是不是也是java>.NET。明显不是。
经常在论坛和贴吧中看到新手问:哪种语言好学,入门快?哪种语言工资高?哪种语言最流行?做应用还是做系统赚钱多?却很看到有人问哪种语言适合自己,哪种语言值得自己作为主语言搞深搞透。语言是我们程序员谋生的工具,做什么事情用什么工具,既然是工具,就一定是五花八门,琳琅满目,“尺有所短,寸有所长”,没有人会因为菜刀不能理发而说菜刀比不上剃头刀。哪种语言都有高工资和低工资,有技术菜鸟也有技术大牛,我们不会因为C语言不能做出漂亮的网页而鄙视它吧,既然如此,为什么要拿.NET、java一较长短呢,就是因为这两门语言很多相同之处吗?PS:如果想多赚钱,还没人跟你竞争,学Erlang吧!
闲话少说,我们来列举下,一个程序员的工资,和什么因素有关?
1、所用语言
2、所处城市,一线、二线、三线、县城、农村
3、个人能力
4、公司
……
看看吧,我没费力就想出了四种影响收入的因素。如果觉得自己用的语言工资低没前途怎么办?参考上面列出的四个因素,我们再来列举几个解决办法
1.换语言,换一门你觉得工资高的,简单易学易用的
2.换城市,从二三线换到一线
3.努力提高自己的技术水平和业务能力,差点忘了一个“做人”的能力
4.说服老板给自己加工资
三、总结
最后,一门语言的好坏,要在特定环境下进行量化的评判,语言的入门程度,配置繁简只是这个语言本身的特性,和你的收入、幸福感没有关系。最近业务原因需要做Swing客户端,看到Swing这个东东,有些同学又要说已经淘汰了,界面丑陋之类了吧?对于自己没有深入的领域不要妄下定论,这是对他人和自己的不负责任。
下午还要去和客户谈项目,突然想到java报价相对于.NET的劣势。吁噫兮,报价难,难于上青天。