Ada:受保护对象死锁

时间:2014-09-09 16:11:40

标签: concurrency ada

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

test.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;

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互相“知道”。 Gate的{​​{1}}条目充当Try_To_Do_Something Foo程序的“网关”。默认情况下Do_Something已被锁定,但拨打Try_To_Do_Something的{​​{1}}会将其解锁,并允许所有等待的来电通过并致电Foo

这种行为可能看起来很奇怪,但这只是重现问题的一个示例。在真正的程序中,我有许多门,UnlockGate程序打开一些并根据其参数关闭其他程序。基本上,一系列Do_Something用于调解对UnlockGate过程的访问权限,具体取决于Gate的内部状态。

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

我不明白为什么。问题是Gate_User在调用Task_User之后就停止了,并且永远不会从Foo_User返回。为什么它会在通话中停止?电话中甚至没有更多的陈述,为什么不回来?当然,Child_Gate.Unlock尝试制作的UnlockGate来电不会通过Do_Something仍在“Gate_User受保护的电话内”,我理解,但是我没有得到的是为什么Foo_User电话不会返回。有什么想法吗?

编辑:我在Kubuntu 14.04上使用GNAT 2014编译并运行它。
编辑2:刚做了进一步的测试。 这只发生在Linux上!在Windows上它正确执行。
编辑3:pstack的结果:

UnlockGate

3 个答案:

答案 0 :(得分:1)

编辑:我更仔细地研究了这一点(在意识到Can_Do_Something总是true之后),我想我已经弄明白了问题的真正原因。

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

  

如果呼叫是内部呼叫(见9.5),子程序的主体将执行正常的子程序调用。如果调用是外部调用,则子程序的主体将作为目标受保护对象上新的受保护操作的一部分执行...

外部调用基本上是一个使用对象的调用,而不是受保护体中的受保护子程序,而只是调用同一主体中的另一个子程序,这是一个内部调用。以上引用的含义是,如果PObj1中受保护的子程序调用{​​{1}}中的受保护子程序,然后调用PObj2中受保护的子程序,则会出现死锁。召集第一个子程序的人开始对PObj1采取受保护的行动。当PObj1然后尝试在PObj2中调用子程序时,这是一个外部调用,因此它尝试启动一个新的受保护操作,由于仍然在{上发生受保护的操作,因此无法执行此操作{1}}。这样做的结果是,你的代码依赖于两个受保护的对象作为同一个受保护动作的一部分相互调用,并不能正常工作。 (这可能是程序在Windows上运行的编译器错误。)

此外,您的代码是错误的,因为受保护的条目体(PObj1)正在进行受保护的条目调用(PObj1)。受保护的操作不应调用可能阻塞的操作(RM 9.5.1(8-16)),并且条目调用是潜在的阻塞操作。受保护类型的预期用途是"受保护的行为"应该在相对较短的时间内执行;他们不应该等待任何事情。所以你不应该这样做(即使现在编写代码的方式,入口调用永远不会阻止)。

RM 9.5.1是here

答案 1 :(得分:0)

确定。我想我已经明白了。这就是:

  • Gate_User阻止Foo.Try_To_Do_Something(等待解锁)
  • Foo_User调用Foo.UnlockGate并使用Foo互斥锁。
  • Foo_User调用Gate.Unlock并获取Gate互斥锁。
  • Foo_User已解锁为True
  • Foo_User(在释放Gate互斥锁之前)评估Try_To_Do_Something的障碍条件。
  • Foo_User调用Try_To_Do_Something,就好像它是Gate_User一样。 (允许在它返回之前执行此操作并释放Gate互斥锁(然后是Foo互斥锁),作为减少任务切换的优化,如果屏障条件变为真且队列中有任务进入)
  • Foo_User阻止Foo.Do_Something,因为它已经使用了Foo互斥锁。

答案 2 :(得分:0)

从受保护的机构发出可能阻止的呼叫总是错误的。也许更改为任务,或在过程调用中使用监视器或信号量(可以实现为受保护类型)?我喜欢Booch 95显示器并锁定。