假设我在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
的{{1}}条目充当Try_To_Do_Something
Foo
程序的“网关”。默认情况下Do_Something
已被锁定,但拨打Try_To_Do_Something
的{{1}}会将其解锁,并允许所有等待的来电通过并致电Foo
。
这种行为可能看起来很奇怪,但这只是重现问题的一个示例。在真正的程序中,我有许多门,UnlockGate
程序打开一些并根据其参数关闭其他程序。基本上,一系列Do_Something
用于调解对UnlockGate
过程的访问权限,具体取决于Gate
的内部状态。
代码中的注释显示Do_Something
和Foo
任务冻结的位置。
我不明白为什么。问题是Gate_User
在调用Task_User
之后就停止了,并且永远不会从Foo_User
返回。为什么它会在通话中停止?电话中甚至没有更多的陈述,为什么不回来?当然,Child_Gate.Unlock
尝试制作的UnlockGate
来电不会通过Do_Something
仍在“Gate_User
受保护的电话内”,我理解,但是我没有得到的是为什么Foo_User
电话不会返回。有什么想法吗?
UnlockGate
答案 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显示器并锁定。