[英]How to make sure two actions are never executed by different threads at the same time?
假设我有两个函数, doAction1()
和doAction2()
。
这两个函数都可以由多个线程执行。
允许doAction1()
的多个并发激活,并且doAction2()
的多个并发激活是允许的,但我不想让 doAction1() 的任何激活与doAction1()
doAction2()
激活重叠。 换句话说,如果任何线程想要执行doAction1()
,它应该等到doAction2()
的所有当前执行完成,或者其他方式。
如果我只是想让doAction2()
总是在doAction1()
之后执行,我可以使用 Phaser 之类的东西。 有没有像 Phaser 这样的东西在两个方向都有依赖关系? 一直被困在执行一个动作中并不是一个问题。
我相信我可以在这两个操作上都有线程计数器之类的东西,并且有一个逻辑来等到没有其他操作的线程处于活动状态,但我不喜欢这个解决方案。
我对多线程没有太多经验,这里有人可以帮忙吗?
好吧,doAction2 需要设置一个 doAction2 本身不需要的锁定条件,但它会冻结任何 doAction1 调用。
这比最初听起来要复杂得多,因为您已经为竞争条件做好了准备。
这是一种使用原始操作的策略:
private final Object locker = new Object();
private int a1 = 0, a2 = 0;
public void doAction1() {
synchronized (locker) {
//one central lock to avoid race conditions.
while (a1 > 0) locker.wait();
a2++;
}
try {
doAction1ForReal();
} finally {
synchronized (locker) {
a2--;
locker.notifyAll();
}
}
}
dA2 也一样。 当然,您可以使用 juc 中的 Lock 对象,但不确定它会如何变得更干净 - 您需要保证不会死锁(其中 a1 和 a2 都是非 0,这将是一个死锁)。 可以肯定的是,这不可能导致死锁:在 a1 为 0 之前,a2 不可能递增。
我同意@Bohemian 关于倒计时不公平的评论。 我使用 Ada 语言制作了两种不同的解决方案,因为我比 Java 更了解 Ada。 下面的第一个示例使用受保护的 object 为每个操作的调用提供一种获取和释放语义的形式。 这个例子是完全不公平的,只运行一个动作,直到调用该动作的所有任务完成。
本例中受保护的 object 封装在 Ada package 中。 第一个文件显示了 package 规范,其中包含受保护的 object 的接口。
package protected_control is
protected Controller is
entry Action_1_Get;
procedure Action_1_Release;
entry Action_2_Get;
procedure Action_2_Release;
private
A1_Count : Natural := 0;
A2_Count : Natural := 0;
end Controller;
end protected_control;
受保护的 object Controller 的实现包含在 package 主体文件中。
package body protected_control is
----------------
-- Controller --
----------------
protected body Controller is
------------------
-- Action_1_Get --
------------------
entry Action_1_Get when A2_Count = 0 is
begin
A1_Count := A1_Count + 1;
end Action_1_Get;
----------------------
-- Action_1_Release --
----------------------
procedure Action_1_Release is
begin
if A1_Count > 0 then
A1_Count := A1_Count - 1;
end if;
end Action_1_Release;
----------------------
-- Action_2_Control --
----------------------
entry Action_2_Get when A1_Count = 0 is
begin
A2_Count := A2_Count + 1;
end Action_2_Get;
----------------------
-- Action_2_Release --
----------------------
procedure Action_2_Release is
begin
if A2_Count > 0 then
A2_Count := A2_Count - 1;
end if;
end Action_2_Release;
end Controller;
end protected_control;
用于测试此 package 的“主要”程序是:
with Ada.Text_IO; use Ada.Text_Io;
with Protected_Control; use Protected_Control;
procedure Protected_test is
procedure A1 is
begin
Put_Line("Starting procedure A1");
delay 0.6;
Put_Line("Ending procedure A1");
end A1;
procedure A2 is
begin
Put_Line("Starting procedure A2");
delay 0.5;
Put_Line("Ending procedure A2");
end A2;
task type run_a1;
task body run_a1 is
begin
for I in 1..10 loop
Controller.Action_1_Get;
A1;
Controller.Action_1_Release;
end loop;
delay 0.01;
for I in 1..5 loop
Controller.Action_1_Get;
A1;
Controller.Action_1_Release;
end loop;
end run_A1;
task type run_A2;
task body run_A2 is
begin
for I in 1..10 loop
Controller.Action_2_Get;
A2;
Controller.Action_2_Release;
end loop;
end run_A2;
A1_runners : array (1..5) of run_A1;
A2_Runners : array (1..5) of run_A2;
begin
null;
end Protected_Test;
结果显示了所有 action_1 活动,然后是所有 action_2 活动,然后是 action_1 活动。
更“公平”的实现使用 Ada 任务入口调用。 Ada 任务条目是一种提供任务之间直接同步通信的机制。
协调任务的 package 规范是:
package Rendezvous_Control 是任务 Do_Actions 是入口 Action_1; 条目 Action_2; 结束 Do_Actions;
结束 Rendezvous_Control;
执行控制任务的 package 主体是:
with Ada.Text_IO; use Ada.Text_IO;
package body Rendezvous_Control is
----------------
-- Do_Actions --
----------------
task body Do_Actions is
Action_1_Count : Natural := 0;
Action_2_Count : Natural := 0;
procedure A1 is
begin
Action_1_Count := Action_1_Count + 1;
Put_Line("Starting action 1");
delay 0.5;
Put_Line("Ending action 1");
Action_1_Count := Action_1_Count - 1;
end A1;
procedure A2 is
begin
Action_2_Count := Action_2_Count + 1;
Put_Line("Starting action 2");
delay 0.6;
Put_Line("Ending action 2");
Action_2_Count := Action_2_Count - 1;
end A2;
begin
loop
if Action_2_Count = 0 then
select
accept Action_1;
A1;
or
terminate;
end select;
end if;
if Action_1_Count = 0 then
select
Accept Action_2;
A2;
or
terminate;
end select;
end if;
end loop;
end Do_Actions;
end Rendezvous_Control;
用于测试这种方法的“主要”程序是:
with Rendezvous_Control; use Rendezvous_Control;
procedure Main is
task type t1;
task body T1 is
begin
for I in 1..10 loop
Do_Actions.Action_1;
end loop;
end T1;
task type t2;
task body T2 is
begin
for I in 1..10 loop
Do_Actions.Action_2;
end loop;
end T2;
T1_Arr : array (1..5) of T1;
T2_Arr : array (1..5) of T2;
begin
null;
end Main;
该版本output的a部分(为简洁起见)如下所示:
Starting action 1 Ending action 1 Starting action 2 Ending action 2 Starting action 1 Ending action 1 Starting action 2 Ending action 2 Starting action 1 Ending action 1 Starting action 2 Ending action 2 Starting action 1 Ending action 1 Starting action 2 Ending action 2
完整版只是遵循上面的模式,在调用动作 1 和动作 2 之间交替。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.