Concurrency Ada:受保护对象死锁
假设我在Ada中有以下代码: test.adsConcurrency 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
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互斥锁李>Foo.UnlockGate
调用Foo_User
并获取Gate互斥锁李>Gate.Unlock
将解锁设置为Foo_User
True
(在释放门互斥之前)评估Foo\u User
的障碍条件李>Try\u To\u Do\u Something
调用Foo\u User
就像它是尝试做某事
。(允许在返回并释放门互斥体(然后是Foo互斥体)之前执行此操作,作为减少任务切换的优化,如果屏障条件变为真,并且条目队列中有任务)Gate\u User
阻塞Foo\u User
,因为它已经使用了Foo互斥锁Foo.Do\u Something
可以做某事之后,
总是正确的),我想我已经找到了问题的真正原因
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使用率是多少