分享

小白科普:死锁

 若思天下 2021-03-18

什么叫死锁?

死锁指多个线程因竞争资源而造成的一种互相等待,若无外力作用,这些进程都将无法向前推进。

业务场景可以分为两大类,单用户业务和用户间业务,一般单用户业务很少出现死锁

死锁的现象?

在DB2的数据库中,后台报错,SQLCODE = -911 & SQLSTATES = 40001

-911 : The current unit of work has been rolled back due to deadlock or timeout.

The most common reason codes are :

· 00C90088 - deadlock.

· 00C9008E - timeout.

Mainframe中,可以通过DB2MSTR的JESMSGLG查锁表明、时间等基本信息。

另外,在OMEGAMON XE FOR DB2 PERFORMANCE EXPERT(死锁日志)中有更明确的信息,比如锁的级别、交叉锁的表名、导致锁表的SQL...(听朋友说的,不过没自己玩过,有机会再琢磨一下)

死锁的原因?

1) 系统资源的竞争.

2) 进程推进顺序非法.

3) 信息量不对等.如进程间彼此相互等待对方发来的消息,结果也会使得这 些进程间无法继续向前推进。例如,进程A等待进程B发的消息,进程B又在等待进程A 发的消息,即进程A和B不是因为竞争同一资源,而是在等待对方的资源导致死锁。

产生死锁的必要条件?

1.互斥条件进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源。

2.不剥夺条件是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放。

3.请求和保持条件进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放。

4.循环等待条件是指进程发生死锁后,必然存在一个进程--资源之间的环形链。

注:4个条件同时满足才会出现死锁。

eg.

系统中有两台输出设备,P0占有一台,PK占有另一台,且K不属于集合{0, 1, ..., n}。

Pn等待一台输出设备,它可以从P0获得,也可能从PK获得。

此,虽然Pn、P0和其他 一些进程形成了循环等待圈,但PK不在圈内,若PK释放了输出设备,则可打破循环等待, 如图2-16。

因此循环等待只是死锁的必要条件。

图片

如何避免死锁?

1.加锁顺序(线程按照一定的顺序加锁)
2.加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)

3.银行家算法允许进程动态地申请资源,用于银行系统现金贷款的发放而得名

锁顺序

当多个线程需要相同的一些锁,但是按照不同的顺序加锁,死锁就很容易发生。
如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生。

但是,这种方式需要你事先知道所有可能会用到的锁(注:并对这些锁做适当的排序)。

如,对现有系统进行全面分析,那么需要先要列出所以业务场景,促发交易跟踪日志才能清楚锁表的顺序,但总有些时候是无法预知的。

eg:

Thread 1:

    lock A

    lock B

Thread 2:

    wait for A

    lock C (when A locked)

Thread 3:

    wait for A

    wait for B

    wait for C

例如

1.线程2和线程3只有在获取了锁A之后才能尝试获取锁C(注:获取锁A是获取锁C的必要条件)。

因为线程1已经拥有了锁A,所以线程2和3需要一直等到锁A被释放。然后在它们尝试对B或C加锁之前,必须成功地对A加了锁。

2.线程3需要一些锁,那么它必须按照确定的顺序获取锁。它只有获得了从顺序上排在前面的锁之后,才能获取后面的锁。

加锁时限

方法是在尝试获取锁的时候加一个超时时间,意味着在尝试获取锁的过程中若超过了这个时限该线程则放弃对该锁请求。(注:加锁超时后可以先继续做其他交易,再回头来重复之前的业务)。

eg:

Thread 1: locks A

Thread 2: locks B

Thread 1: attempts to lock B but is blocked

Thread 2: attempts to lock A but is blocked

Thread 1: lock attempt on B times out

Thread 1: backs up and releases A as well

Thread 1: waits randomly(257 millis) before retrying.

Thread 2: lock attempt on A times out

Thread 2: backs up and releases B as well

Thread 2: waits randomly(57 millis) before retrying.

例如

1.线程2比线程1早200毫秒进行重试加锁,先成功锁表B&A,此时线程1尝试获取锁A并且处于等待状态。

2.如果只有两个线程,并且重试的超时时间设定为0到500毫秒之间,一般不会超时。

3.超时和重试机制是为了避免在同一时间出现的竞争,当线程过多是出现问题的概率也越大。

注:可能存在锁表的超时,但锁表超时不一定是死锁,也可能因为线程过多需要长时间处理。

银行家算法

eg:

系统有三个进程P1、P2和P3,共有12台打印机。
进程P1总共要求10台打印机,P2和P3分别要求4台和9台
假定T0时刻,进程P1、P2和P3已分别获得5台、2台和2台,尚有3台空闲未分。如下:

进程最大需求已分配还需要可用
P110553
P2422
P3927

即,存在一个安全序号P2、P1、P3,在T0时刻系统安全。

注:安全序号是指一个进程序列{P1,…,Pn}是安全的,即对于每一个进程Pi(1≤i≤n),它以后尚需要的资源量不超过系统当前剩余资源量与所有进程Pj (j < i )当前占有资源量之和。(即在分配过程中,不会出现某一进程后续需要的资源量比其他所有进程及当前剩余资源量总和还大的情况)

原理:在满足当前进程P1的资源申请后,是否还有足够的资源去满足下一个距最大资源需求的进程(P2),即进程P2最大需求是4,已分配2个,还需2个,则检查通过,将继续检查下一个距最大资源需求的进程(P3),若能满足所有进程则表示安全。

注:检查过程中,一个进程检查通过进入下一个进程检查时,当前可分配资源为回收上一个进程资源总值。即当一个进程检查通过表示该进程已结束,资源可回收。

死锁状态是不安全状态,而不安全状态不一定是死锁状态,但不安全状态可能导致死锁。

也就是说当进程申请一个可用的资源时,系统必须确定这一资源申请是可以立即分配还是等待。只有分配后使系统仍处于安全状态,才允许申请。

eg2:

业务背景在银行客户申请贷款的数量是有限的,每个客户在第一次申请贷款时要声明完成该项目所需的最大资金量,在满足所有贷款要求时,客户应及时归还。银行家在客户申请的贷款数量不超过自己拥有的最大值时,都应尽量满足客户的需要。在这样的描述中,银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。

与eg1一样避免死锁方法中允许进程动态地申请资源,系统在进行资源分配之前,先计算资源分配的安全性。若此次分配不会导致系统进入不安全状态,则将资源分配给进程;否则,进程等待。

图片


Available[ ]矩阵数组表示某类资源的可用量。
Claim[ i ][ j ]表示进程Pi最大需要Rj类资源的数量。
Allocation[ i ][ j ]表示Pi已占有的Rj类资源数量。
Need[ i ][ j ]表示Pi尚需Rj类资源的数量如下表:

Need[ i ][ j ]=Claim[ i ][ j ]—Allocation[ i ][ j ]

Request[ i ]表示进程Pi进程的申请向量,如 Request[ i ][ j ]=m 表示Pi申请m个Rj类资源

对于当前进程Pi X

(1) 检查if( Request[ i ][ j ]<=Need[ i ][ j ] ) goto (2)
else error(“进程 i 对资源的申请量大于其说明的最大值 ”);
(2) 检查 if ( Request[ i ][ j ]<=Available[ j ] ) goto (3)
else wait() ; /*注意是等待!即在对后续进程的需求资源判断中,若出现不符合的则安全检查结束,当前进程进入等待*/
(3) 系统试探地把资源分给Pi 并修改各项属性值 (具体是否成立,则根据安全检查的结果)
Available[ j ]  =Available[ j ] — Request[ i ][ j ]
Allocation[ i ][ j ]=Allocation[ i ][ j ] +Request[ i ][ j ]
Need[ i ][ j ]=Need[ i ][ j ]— Request[ i ][ j ]

(4) 安全检查,若检查结果为安全,则(3)中执行有效,否则分配作废,使该Pi进程进入等待
检查算法描述:
向量Free[ j ]表示系统可分配给各进程的Rj类资源数目,初始与当前Available等值
向量Finish[ i ]表示进程Pi在此次检查中是否被满足,初始均为false 当有足有资源可分配给进程时,
Finish[ i ]=true, Pi完成并释放资源(Free[ j ]+=Allocation[ i ][ j ])
1) 从进程队列中找一个能满足下述条件的进程Pi
    ①、Finish[ i ]==false,表示资源未分配给Pi进程
    ②、Need[ i ][ j ]<Free[ j ],表示资源足够分配给Pi进程
2) 当Pi获得资源后,认为Pi完成,释放资源
Free[ j ]+=Allocation[ i ][ j ];
Finish[ i ]=true;
goto Step 1);
如:
int trueSum=0, i=0 ; boolean Flag=true;
while( trueSum<P.length-1 && Flag==true ){
i=i%P.length;
if( Finish[ i ]==false ){
if(Need[ i ][ j ]<Free[ j ]){
Free[ j ]+=Allocation[ i ][ j ];
Finish[ i ]=true;
trueSum++;
i++;
}else{
Flag=false;
}
}    }
if( Flag==false)
检查不通过,拒绝当前进程X的资源申请
else
检查通过,允许为当前进程X分配资源
即若可达到Finish[ 0,1,2,......n ] ==true 成立则表示系统处于安全状态
再如:
有5个进程{P1,P2,P3,P4,P5} 。4类资源{R1,R2,R3,R4} 各自数量为6、3、4、2
T0时刻各进程分配资源情况如下

图片
T0时刻为安全状态,存在安全序列{P4,P1,P2,P3,P5} 如下:
图片
注:银行家算法在避免死锁角度上非常有效,但是需要在进程运行前就知道其所需资源的最大值,且进程数也通常不是固定的,因此使用有限,但从思想上可以提供了解,可以转换地应用在其他地方。

案例分析?

1.活期转账

业务背景:系统同时发起两笔交易,一个交易是DR DDAC,CR IBAC。另一笔交易DR IBAC,DR DDAC。两笔交易的IB AC相同,DD账户不同,但是由于DD表属性设置为页锁,导致在同一时间相邻的活期账号形成等待。交易运行时第一笔交易先锁了IB,第二笔交易先锁了DDAC。程序继续运行,第一笔交易要去锁DDAC时发现已经被第二笔交易锁了。第二笔交易要去锁DDTMST时发现已经被第一笔交易锁了。由此造成了交叉锁。

分析思路:

1.检查DB2 log确认表名和交易时间;

2.通过DDAC/IBAC查系统日志,确认具体交易代码;

3.结合程序,关注READ UPDATE的地方;

4.检查DD表属性(页锁)

    SELECT LOCKRULE,CREATOR FROM SYSIBM.SYSTABLESPACE WHERE NAME = 'XXXX';;  

5.尝试将表属性由页锁改为行锁(ANY-->ROW),进行测试

    ALTER TABLESPACE DCNDBBP.BPTTLT LOCKSIZE ROW;

6.最后还是优化程序READ UPDATE的处理。不建议修改表属性,因为会严重影响效率。

2.过渡内部户..

3.热点账户..

4. ......

今天先写到这里。

END


想获取更多知识

扫二维码添加

↓↓

图片

世界那么大

而我只想遇见你

你的关注是我们的动力!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多