简体   繁体   English

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

[英]How to make sure two actions are never executed by different threads at the same time?

Let's assume I have two functions, doAction1() and doAction2() .假设我有两个函数, doAction1()doAction2()

Both of those functions can be executed by multiple threads.这两个函数都可以由多个线程执行。

Multiple, concurrent activations of doAction1() are allowed, and multiple concurrent activations of doAction2() are allowed, but I do not want to allow any activation of doAction1() to overlap an activation of doAction2() .允许doAction1()的多个并发激活,并且doAction2()的多个并发激活是允许的,但我不想让 doAction1() 的任何激活与doAction1() doAction2()激活重叠。 In other words, if any thread want to execute doAction1() , it should wait until all current executions of doAction2() are finished, and other way around.换句话说,如果任何线程想要执行doAction1() ,它应该等到doAction2()的所有当前执行完成,或者其他方式。

If I just wanted doAction2() always to be executed after doAction1() , I could use something like Phaser.如果我只是想让doAction2()总是在doAction1()之后执行,我可以使用 Phaser 之类的东西。 Is there something like Phaser with dependencies in both directions?有没有像 Phaser 这样的东西在两个方向都有依赖关系? Getting stuck in executing one action all the time is not a concern.一直被困在执行一个动作中并不是一个问题。

I believe I could have something like thread counters on both actions and have a logic to wait until no threads with other action are active, but I do not like this solution.我相信我可以在这两个操作上都有线程计数器之类的东西,并且有一个逻辑来等到没有其他操作的线程处于活动状态,但我不喜欢这个解决方案。

I do not have much experience with multithreading, can someone here help?我对多线程没有太多经验,这里有人可以帮忙吗?

Well, doAction2 needs to set a lockdown condition which doAction2 itself doesn't need, but which freezes any doAction1 calls.好吧,doAction2 需要设置一个 doAction2 本身不需要的锁定条件,但它会冻结任何 doAction1 调用。

This is considerably more complicated than it initially sounds because you're set up for a race condition.这比最初听起来要复杂得多,因为您已经为竞争条件做好了准备。

Here is one strategy, using primitive operations:这是一种使用原始操作的策略:

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();
        }
    }
}

and the same for dA2. dA2 也一样。 You can use Lock objects from juc of course, but not sure how it would end up any cleaner - you want guarantees that you can't deadlock (where a1 and a2 are both non-0, that'd be a deadlock here).当然,您可以使用 juc 中的 Lock 对象,但不确定它会如何变得更干净 - 您需要保证不会死锁(其中 a1 和 a2 都是非 0,这将是一个死锁)。 Pretty sure this take cannot possibly result in deadlock: a2 cannot possibly be incremented until a1 is 0.可以肯定的是,这不可能导致死锁:在 a1 为 0 之前,a2 不可能递增。

You can build a solution with these patterns:您可以使用以下模式构建解决方案:

Bundle the operations as commands that are put into a Queue .将操作捆绑为放入Queue的命令。 A single consumer, running in its own thread, processes each in turn via an ExecutorService .单个消费者在自己的线程中运行,通过ExecutorService依次处理每个消费者。 However, it keeps track of the type of the previous item processed (the semaphore);但是,它会跟踪上一个处理的项目的类型(信号量); if the next item is not of the same type, it blocks waiting for all processing to complete via a CountDownLatch that the consumer increments before processing and that the processor decrements on completion.如果下一个项目不是同一类型,它会阻塞等待所有处理通过CountDownLatch完成,消费者在处理之前递增,处理器在完成时递减。

This approach is fair: One type cannot swamp the other.这种方法是公平的:一种类型不能淹没另一种。

This is an asynchronous approach, but can be made synchronous by incorporating a callback to the command.这是一种异步方法,但可以通过将回调合并到命令来使其同步。

I agree with the comment @Bohemian made about the count downs being unfair.我同意@Bohemian 关于倒计时不公平的评论。 I produced two different solutions using the Ada language because I know Ada better than I know Java.我使用 Ada 语言制作了两种不同的解决方案,因为我比 Java 更了解 Ada。 The first example below uses a protected object to provide a form of acquire and release semantics for calls on each action.下面的第一个示例使用受保护的 object 为每个操作的调用提供一种获取和释放语义的形式。 That example is completely unfair, running only one action until all the tasks calling that action complete.这个例子是完全不公平的,只运行一个动作,直到调用该动作的所有任务完成。

The protected object in this example is encapsulated in an Ada package.本例中受保护的 object 封装在 Ada package 中。 The first file shows the package specification, which contains the interface to the protected object.第一个文件显示了 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;

The implementation of the protected object Controller is contained in the package body file.受保护的 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;

The "main" procedure used to test this package is:用于测试此 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;

The results show all action_1 activities in a bunch followed by all action_2 activities, followed by action_1 activities.结果显示了所有 action_1 活动,然后是所有 action_2 活动,然后是 action_1 活动。

A more "fair" implementation uses Ada task entry calls.更“公平”的实现使用 Ada 任务入口调用。 Ada task entries are a mechanism providing direct synchronous communication between tasks. Ada 任务条目是一种提供任务之间直接同步通信的机制。

The package specification for the coordinating task is:协调任务的 package 规范是:

package Rendezvous_Control is task Do_Actions is entry Action_1; package Rendezvous_Control 是任务 Do_Actions 是入口 Action_1; entry Action_2;条目 Action_2; end Do_Actions;结束 Do_Actions;

end Rendezvous_Control;结束 Rendezvous_Control;

The package body implementing the controlling task is:执行控制任务的 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;

The "main" procedure used to test this approach is:用于测试这种方法的“主要”程序是:

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;

The a section of output of this version (truncated for brevity) is shown below:该版本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

The full version simply follows the pattern above, alternating between calling action 1 and action 2.完整版只是遵循上面的模式,在调用动作 1 和动作 2 之间交替。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使两个线程同时工作 - How to make two threads work in same time 如何确保线程被完全执行? (使用线程池) - How to make sure threads are being fully executed? (using thread pool) 如何确保每次执行作业时 MultiResourceItemReader 都会刷新资源 - How to make sure the MultiResourceItemReader refreshes the resources each time the job is executed 两个线程可以在同一时间运行两种不同的方法吗? - Can two threads run two different methods at the same point of time? 如何“完全”同时启动两个线程 - How to start two threads at “exactly” the same time 如何在java中同时运行两个线程 - How to run two threads at the same time in java 如何确保写入发生在同一存储桶和同一键上的不同线程同时读取并发哈希映射之前? - How can I make sure that write happens before read in concurrenthashmap at the same time by different thread at same bucket and on a same key? 同时从两个不同的线程插入/更新实体 - Insert/update entitiy from two different threads at the same time 两个线程可以分配给不同的处理器并同时执行吗? - Can two threads be assigned to different processors and execute at the same time? 如何同时(或在关闭时)启动两个线程 - how to start two threads at the same time(or at the close time)
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM