阿达:受保护对象死锁



假设我在Ada中有以下代码:

测试.ads

        package Test is
       type Gate; --forward declaration for mutual use
       protected type Foo is
          entry Do_Something;
          procedure UnlockGate;
          procedure Initialize(child : access Gate; me : access Foo);
       private
          Can_Do_Something : Boolean := True;
          Child_Gate : access Gate;
       end Foo;
       protected type Gate is
          entry Try_To_Do_Something;
          procedure Unlock;
          procedure SetParentFoo(parent : access Foo);
       private
          unlocked : Boolean := False;
          Parent_Foo : access Foo;
       end Gate;
       task Foo_User;
   task Gate_User;
   task Init;
   foo_inst : access Foo;
   gate_inst : access Gate;
end Test;

测试.adb

    with Ada.Text_IO; use Ada.Text_IO;
package body Test is
   protected body Foo is
      entry Do_Something when Can_Do_Something is
      begin
         --Do something
         null;
      end Do_Something;
      procedure UnlockGate is
      begin
         Child_Gate.Unlock;
         --Foo_User freezes here. It will make the call to Child_Gate.Unlock but just after that it will stop
      end UnlockGate;
      procedure Initialize(child : access Gate; me : access Foo) is
      begin
         Child_Gate := child;
         Child_Gate.SetParentFoo(me);
      end Initialize;
   end Foo;
   protected body Gate is
      entry Try_To_Do_Something when unlocked is
         pragma Warnings(Off);
      begin
         --Gate_User freezes here, it enters the procedure but will not make the Do_Something call
         Parent_Foo.Do_Something;
      end Try_To_Do_Something;
      procedure Unlock is
      begin
         unlocked := True;
      end Unlock;
      procedure SetParentFoo(parent : access Foo) is
      begin
         Parent_Foo := parent;
      end SetParentFoo;
   end Gate;
   task body Init is
   begin
      foo_inst := new Foo;
      gate_inst := new Gate;
      foo_inst.Initialize(gate_inst, foo_inst);
   end Init;
   task body Gate_User is
   begin
      delay 1.0;
      Put_Line("Gate_User is trying to do something");
      gate_inst.Try_To_Do_Something;
      Put_Line("This statement will never be reached, Gate_User task freezes...");
   end Gate_User;
   task body Foo_User is
   begin 
      delay 2.0;
      Put_Line("Foo_User is unlocking the gate");
      foo_inst.UnlockGate;
      Put_Line("This statement will never be reached, Foo_User task freezes...");
   end Foo_User;
end Test;

main.adb

with Test; use Test; --This will start the tasks
procedure main is
   begin
      null; --Nothing to do here
end main;

让我解释一下这里发生了什么。GateFoo相互"了解"。GateTry_To_Do_Something条目充当FooDo_Something过程的"网关"。默认情况下,Try_To_Do_Something被锁定,但对FooUnlockGate的调用将解锁它,并允许所有等待的调用通过并调用Do_Something

这种行为可能看起来很奇怪,但这只是重现问题的一个样本。在实际程序中,我有很多门,UnlockGate程序根据其参数打开一些门,关闭其他门。基本上,根据Foo的内部状态,使用一系列Gate来调解对Do_Something过程的访问。

代码中的注释显示了Gate_UserTask_User任务冻结的位置。

我不明白的是为什么。问题是Foo_User在调用Child_Gate.Unlock之后立即停止,并且从未从UnlockGate返回。为什么它会在通话过程中停下来?通话中甚至没有更多的语句,为什么它没有返回?当然,Gate_User试图进行的Do_Something调用没有通过,因为Foo_User仍然"在"对UnlockGate的受保护调用中,我理解这一点,但我不明白为什么UnlockGate调用没有返回。有什么想法吗?

编辑:我在Kubuntu 14.04上使用GNAT 2014来编译和运行这个
编辑2:刚刚做了进一步的测试这种情况只在Linux上发生!在窗口上,它执行正确
编辑3:pstack结果:

31779: ./main
(No symbols found in )
(No symbols found in /lib/i386-linux-gnu/libc.so.6)
(No symbols found in /lib/ld-linux.so.2)
crawl: Input/output error
Error tracing through process 31779

EDIT:我对此进行了更深入的研究(在意识到Can_Do_Something始终是true之后),我想我已经找到了问题的真正原因。

RM 9.5.1(3)讨论了受保护子程序调用的语义:

如果调用是内部调用(请参阅9.5),则子程序的主体将像普通子程序调用一样执行。如果调用是外部调用,则子程序主体将作为对目标受保护对象的新受保护操作的一部分执行。。。

外部调用基本上是使用对象的调用,而不是受保护主体中的受保护子程序只调用同一主体中的另一个子程序,这是一个内部调用。上面引用的意思是,如果PObj1中的一个受保护子程序调用PObj2中的受保护子程序,而CCD_28又调用PObj1中的受保子程序,则会出现死锁。调用第一个子程序的人在PObj1上启动了受保护的操作。当PObj2尝试调用PObj1中的子程序时,这是一个外部调用,因此它尝试启动一个新的受保护操作,但由于PObj1上仍有受保护操作发生,因此它无法执行此操作。这样做的结果是,依赖于两个受保护对象作为同一受保护操作的一部分相互调用的代码将无法工作。(该程序在Windows上运行可能是编译器错误。)

此外,您的代码是错误的,因为受保护条目主体(Try_To_Do_Something)正在进行受保护条目调用(Parent_Foo.Do_Something)。受保护的操作不应调用潜在的阻塞操作(RM 9.5.1(8-16)),入口调用是潜在的阻塞作业。受保护类型的预期用途是"受保护的操作"应在相对较短的时间内执行;他们不应该等待任何事情。因此,您不应该这样做(尽管按照现在编写代码的方式,入口调用永远不会阻塞)。

RM 9.5.1在这里。

好的。我想我明白了。事情就是这样:

  • Foo.Try_To_Do_Something上的Gate_User块(等待解锁)
  • Foo_User调用Foo.UnlockGate并获取Foo互斥
  • Foo_User调用Gate.Unlock并获取Gate互斥
  • Foo_User设置为解锁为True
  • Foo_User(在释放Gate互斥之前)评估Try_To_Do_Something的屏障条件
  • CCD_ 46调用CCD_。(在返回并释放Gate互斥体(然后是Foo互斥体)之前,可以这样做,作为减少任务切换的优化,如果障碍条件为真,并且条目的队列中有任务)
  • Foo_UserFoo.Do_Something上阻塞,因为它已经占用了Foo互斥

从受保护的主体进行潜在的阻塞调用总是错误的。也许可以更改为任务,或者在过程调用中使用监视器或信号量(可以作为受保护的类型实现)?我喜欢Booch 95显示器和锁。

最新更新