繁体   English   中英

如何确保两个动作永远不会被不同的线程同时执行?

[英]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 不可能递增。

您可以使用以下模式构建解决方案:

将操作捆绑为放入Queue的命令。 单个消费者在自己的线程中运行,通过ExecutorService依次处理每个消费者。 但是,它会跟踪上一个处理的项目的类型(信号量); 如果下一个项目不是同一类型,它会阻塞等待所有处理通过CountDownLatch完成,消费者在处理之前递增,处理器在完成时递减。

这种方法是公平的:一种类型不能淹没另一种。

这是一种异步方法,但可以通过将回调合并到命令来使其同步。

我同意@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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM