Concurrency Ada:受保护对象死锁

Concurrency Ada:受保护对象死锁,concurrency,ada,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; m

假设我在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;
让我解释一下这里发生了什么。
Gate
Foo
相互“了解”
Gate
尝试做某事
入口充当
Foo
做某事
过程的“网关”<代码>尝试做某事在默认情况下是锁定的,但是调用
Foo
解锁门
将解锁它,并允许所有等待的呼叫通过并调用
做某事

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

代码中的注释显示
Gate\u用户
Task\u用户
任务冻结的位置

我不明白的是为什么。问题是,
Foo\u User
在调用了
Child\u Gate.Unlock
后立即停止,并且从不从
UnlockGate
返回。为什么它在呼叫的中间停止?呼叫中甚至没有任何语句,为什么不返回?当然,
dou\u Something
调用,
Gate\u用户
试图进行的调用没有通过,因为
Foo\u用户
仍然在
UnlockGate
的受保护调用的“内部”,我理解,但我不明白的是为什么
UnlockGate
调用没有返回。有什么想法吗

编辑:我正在Kubuntu 14.04上使用GNAT 2014来编译和运行此文件。
编辑2:刚刚做了进一步的测试这只发生在Linux上!它在windows上正确执行。
编辑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

嗯。我想我明白了。情况就是这样:

  • Gate\u User
    阻止
    Foo。尝试做某事(等待解锁)
  • Foo\u User
    调用
    Foo.UnlockGate
    并获取Foo互斥锁
  • Foo_User
    调用
    Gate.Unlock
    并获取Gate互斥锁
  • Foo_User
    将解锁设置为
    True
  • Foo\u User
    (在释放门互斥之前)评估
    Try\u To\u Do\u Something
    的障碍条件
  • Foo\u User
    调用
    尝试做某事
    就像它是
    Gate\u User
    。(允许在返回并释放门互斥体(然后是Foo互斥体)之前执行此操作,作为减少任务切换的优化,如果屏障条件变为真,并且条目队列中有任务)
  • Foo\u User
    阻塞
    Foo.Do\u Something
    ,因为它已经使用了Foo互斥锁
编辑:我已经更仔细地研究了这一点(在意识到
可以做某事之后,
总是
正确的
),我想我已经找到了问题的真正原因

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

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

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

此外,您的代码是错误的,因为受保护的条目正文(
Try\u Do\u Something
)正在进行受保护的条目调用(
Parent\u Foo.Do\u Something
)。受保护的操作不应调用潜在阻塞操作(RM 9.5.1(8-16)),而入口调用是潜在阻塞操作。受保护类型的预期用途是“受保护的操作”应在相对较短的时间内执行;他们不应该等待任何事情。因此,您不应该这样做(即使按照现在编写代码的方式,entry调用永远不会阻塞)


RM 9.5.1是。

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

您没有提到任何关于编译器/操作系统的内容,这可能与本文相关。您是使用调试器来验证任务是否阻塞,还是仅依赖于文本IO输出?在任何情况下,Can_Do_Something都是未初始化的,可能很容易为False,这解释了为什么Do_Something条目会被阻止。抱歉,编辑了代码,Can_Do_Something是true。事实上,Do_Something guard并没有受到我所做测试的影响,即使它总是正确的,条目也不会被调用。还编辑了带有环境信息的问题,“我在Kubuntu LinuxOK上使用GNAT 2014,你们用调试器验证了吗?”?你是从GPS内部运行你的程序吗?当你的程序冻结时,它的CPU使用率是多少