Page 111 - 《软件学报》2021年第6期
P. 111

鲁法明  等:基于锁增广分段图的多线程程序死锁检测                                                        1685


         第 28 行关于锁 q 和 p 的嵌套获取”与“threadC 在第 35 行和 36 行关于锁 p 和 q 的嵌套获取”不会导致死锁,因
         为 threadB 获取 q 和 p 的前提是持有锁 m,而一旦 threadB 先持有了 m,则 threadC 便会被阻塞在第 34 行获取 m
         的位置,从而无法嵌套获取 p 和 q;反之,threadC 获取 p 和 q 的前提是持有锁 n,而一旦 threadC 先持有了 n,则
         threadB 将被阻塞在第 26 行获取 n 的位置,从而无法嵌套获取 q 和 p.由此可见:虽然 threadB 执行第 28 行获取 p
         的操作时持有的锁集为{m,q},threadC 执行第 36 行获取 q 的操作时持有的锁集为{n,p},两个持有锁集互不相交,
         但是它们之前曾经持有和随后释放的锁对象 n 和 m 却也形成一组门锁,从而消除了这两个操作并发的可能性,
         使得它们不会导致死锁.
                         Table 1  Pseudo-code of a multithreaded program, denoted by Program 1
                                      表 1   多线程程序 Program 1 的伪代码
                                                                   ThreadB:
                                                                   20: poblic void run{
                                                                   21:   synchronized(G){⋅}
                      Main Thread:                                 22:   synchronized(o2){
                      01:public static void main(String[⋅] args) throws InterruptedExce{  23:    synchronized(o1){⋅}
                      02:   flag=0;                                24:  }
                      03:   new threadA.start(⋅);                  25:   synchronized(m){
                      04:   new theradC.start(⋅);                  26:    synchronized(n){⋅}
                      05:   threadA.join(⋅);                       27:    synchronized(q){
                      06:}                                         28:     synchronized(p){⋅}
                                                                   29:   }
                                                                   30:  }
                                                                   31: }
                      ThreadA:
                      07: poblic void run{
                      08:   for (int i=0; i<2; i++){               ThreadC:
                      09:    synchronized(G){                      32: poblic void run{
                      10:     if (flag==0){
                      11:       new threadB.start(⋅);              33:   synchronized(n){
                      12:       flag=1;                            34:    synchronized(m){⋅}
                                                                   35:    synchronized(p){
                      13:    }
                      14:     synchronized(o1){                    36:     synchronized(q){⋅}
                      15:      synchronized(o2){⋅}                 37:   }
                      16:    }                                     38:  }
                                                                   39: }
                      17:   }
                      18:  }
                      19: }

         1.2   动机分析
             如上节所述,Program 1 存在 4 处锁对象嵌套获取构成的循环依赖环路,但只有两处对应真实死锁,另两处不
         是死锁.然而,传统的动态死锁分析方法无法区分上述情况,由此导致死锁误报.
             具体来说,假设 Program 1 某次运行轨迹见表 2.其中,start(u,v)代表线程 u 启动线程 v 的操作,stop(u)代表线
         程 u 的终止操作,join(u,v)代表线程 u 同步等待直至线程 v 终止,acq(t,l)表示线程 t 获取锁 l 的操作,rel(t,l)代表线
         程 t 释放锁 l 的操作.下面以基于分段图和扩展锁图的死锁检测方法为例,说明传统动态分析方法的检测结果.
             文献[17]基于 start 和 join 将源程序的操作分为很多段,并根据各个段在执行顺序上的先后依赖关系构造分
         段图.具体来说,最初仅为主线程添加一个初始段;之后,每当执行操作 start(u,v)时,在线程 u 此操作之处添加一个
         新段,为线程 v 添加一个初始段,并在线程 u 的上一个段与这两个新段之间各添加一条有向弧;每当执行操作
         join(u,v)时,为线程 u 添加一个新段,在 u 的上一个段与此段间添加一条有向弧,并在线程 v 的线程终止段与此段
         间也添加一条有向弧(该有向弧的添加需要待线程 v 执行终止操作时方能添加).对于表 1 的运行轨迹,构造所得
         的分段图如图 1(a)所示.
             除构造分段图外,文献[17]为锁图每条有向弧〈lock 1 ,lock 2 〉扩充标记信息 arcID:〈seg 1 ID,(threadID,lockSet),
         seg 2 ID〉,提出了扩展锁图的概念.其中,arcID 表示弧的唯一 ID,threadID 表示当前锁申请操作所属线程的 ID,
         seg1ID 代表 threadID 获取锁 lock 1 所在的段号,seg 2 ID 代表 threadID 获取锁 lock 2 时所在的段号,lockSet 代表
   106   107   108   109   110   111   112   113   114   115   116