假设我在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;
让我解释一下这里发生了什么。Gate
和Foo
相互"了解"。Gate
的Try_To_Do_Something
条目充当Foo
的Do_Something
过程的"网关"。默认情况下,Try_To_Do_Something
被锁定,但对Foo
的UnlockGate
的调用将解锁它,并允许所有等待的调用通过并调用Do_Something
。
这种行为可能看起来很奇怪,但这只是重现问题的一个样本。在实际程序中,我有很多门,UnlockGate
程序根据其参数打开一些门,关闭其他门。基本上,根据Foo
的内部状态,使用一系列Gate
来调解对Do_Something
过程的访问。
代码中的注释显示了Gate_User
和Task_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_User
在Foo.Do_Something
上阻塞,因为它已经占用了Foo互斥
从受保护的主体进行潜在的阻塞调用总是错误的。也许可以更改为任务,或者在过程调用中使用监视器或信号量(可以作为受保护的类型实现)?我喜欢Booch 95显示器和锁。