死锁:代码里那些让人头破血流的“卡死”时刻 所谓的死锁,说白了就是程序当作自己按下了对的指令,结局卡在某个动作上动弹不得。它不像停机保护那样干脆利落,能让你先断电再开机,死锁往往是那种让你原地死机,键盘仿佛被啥东西死死焊住了一样。 要从啥时候启动质疑程序“要晕那会儿”了呢?得看看那几行代码是不是在跟别人抢东西。
比如咱们在用共享资源,像两个线程都在疯狂读写同一个内存缓冲区,要么两个进程都在获取互斥锁的时候。
这时候要是其中一派的线程先占住了资源,没等着对方松手,自己又去写代码了,那后面占用的线程就只能干瞪眼了。 你有没有见过那种场景,一个员工在排队复印,复印机正好在那一刻全满,他不管再等一秒还是等十秒都干等着,结局旁边另一个员工冲上去占用了那台机器,自己那堆材料还在桌上,整个队伍就彻底瘫痪了。
这种“你占我资源,我不占你资源,你一来我又占你资源”的三角关系,就是典型的死锁。 更绝的是,死锁往往不是形成在业务逻辑上,而是形成在数据不一致要么内存泄漏这种底层恐怖的事件上。
比如两个线程分别锁了两个不同的对象,各自修改了自己的数据,结局发现俩数据都快互换了,当作没死锁,实际上数据已经烂掉了。
这时候哪怕程序持续运行,出来的结局也是错的,就像两个人各说各话,最终连个整个的版本都算不上。 自然,死锁之故此能死,得看它的“脾气”。
要是这次抢资源是弱关系,比如线程只是看了一下数据就松手了,那它自己挺快就能解脱;但要是涉及的资源强关联,比如几个进程互锁又互斥,互相牵制着,那就算工夫过了,它们也可能一辈子僵在那儿,直到某个系统维护人员拿着重启命令把这台机器扔出去。 这时候就要说说为啥有时候明明代码没写错,却还是卡死了。
这就得看看系统设计的水平。假设有个订单处理系统,两个线程与此同时处理订单,都拿了一个锁,然后认定“我的锁自己拿着,你拿你的”,结局两个线程都认定自己是保险的,哪位也没停手,最终订单数据变成了几百个重复的副本,要么两个线程都卡在那个正在处理的订单里出不来。
这不仅是 bug,这是系统架构上的硬伤。 再举个具体的例子,有次测试一个数据库连接池,发现一半线程死锁了,看着都瘆得慌。查了复盘报告,发现是那个线程在执行查询时,没检查有没有锁着,直接拿去申请连接,结局别人正好拿着连接站在那儿呢。他们俩哪位也没发现对方占着茅坑不拉屎,结局就是两个线程互相卡着,连个决断的机会都没有。
这种“你占我资源,我占你的资源,你占我的资源,我占你的资源”的循环,就像在过火路,根本走不了,要不就有人直接拉闸断电。 实际上死锁的根源往往不是代码写得有多深奥,而是对并发场景的想象忒不接地气。大量开发者认定多线程就是干活快,哪位抢到资源哪位先干,结局忽略了资源本身的共享属性。
要是资源是共享的,那就得合计好哪位先干,哪位后干,不能拍脑袋拍板哪位先拿。 并且,死锁有时还会带着“镜像”的恐怖。
要是系统里有大量的死锁,那对整个系统的性能影响是惊人的。就像工地里的钢筋被焊死在一起,最终混凝土都浇不上了,整个项目就废了。
这时候光靠改代码去修复死锁,往往不够,就连可能害得系统崩溃。 最终,想做个小小的补充,别看死锁看起来挺恶心,但在某些情况下,有时候它反而是系统自我保护的机制,要么说是为了保保险而牺牲性能的代价。
比如银行系统为了防止并发访问害得的数据损坏,可能会设计成“哪位先锁哪位先干”,就算这样会害得局部用户排队,但数据绝对保险。权衡一下,有时候“死锁”也不是彻底没用。 总而言之,别让你的程序陷入那种“你占我资源,我占你的资源,你占我的资源,我占你的资源”的死循环。
要么搞清楚哪位先占的,要么干脆让资源释放,别在原地死磕了。
毕竟,代码运行得忒久,让使用者自己感受死锁的滋味,那才是最大的教训。


相关标签: