如何为一系列任务设计执行引擎

时间:2014-12-08 13:27:58

标签: java multithreading concurrency

我正在尝试用Java编写一个问题,我必须执行一堆任务。

问题

执行由多个任务组成的作业,这些任务之间具有依赖关系。

作业将有一个任务列表,每个此类任务将进一步列出后续任务列表(每个后续任务都有自己的后续任务 - 您可以在此处看到递归性质)。如果 -

,每个后继任务都可以开始执行
  1. 它被配置为在部分执行其前任任务时执行。在这种情况下,前任任务将通知它已部分完成并且我的后续任务可以开始

  2. 成功完成其前任任务。

  3. 示例

    有2个初始任务A和B的工作.A有2个后继任务M和N.B有1个后继任务P. P有2个后继任务Y和Z.

    M可以从其前任任务A的部分完成开始。 Z可以开始部分完成其前任任务P. N,P和Y只能在完成其前任任务A,B和P时才能启动。

    Tasks Hierarchy (A and B can start in parallel)

    我必须设计这样的工作流程/工作的执行。在设计中,我们必须确认先前任务发送的部分完成事件,以便可以启动其后继任务。我该怎么办呢?在并发中是否有适合此问题的设计模式?

11 个答案:

答案 0 :(得分:7)

看看akka - http://akka.io

使用akka创建actor(事件驱动,并发实体以异步方式处理消息)

每个任务都可以表示为一个演员(你选择何时开火)

你可以在部分完成或完全完成时触发其他演员(任务)(实际上你可以随时触发它们)

答案 1 :(得分:5)

您的问题看起来像是Java ForkJoin Framework的一个很好的用例。您可以将任务实现为RecursiveActionRecursiveTask(取决于您是否需要返回值),这将在您需要的任何条件下启动其子任务。您还可以控制子任务是按顺序还是并行运行。

示例:

public class TaskA extends RecursiveAction {
  // ...

  protected void compute() {
    if (conditionForTaskM) {
      TaskM m = new TaskM();
      // Run task M asynchronously or use m.invoke() to run it synchronously.
      invokeAll(m);
    }

    // Run task N at the end of A
    invokeAll(new TaskN());
  }

}

您需要ForkJoinPool的实例来运行您的任务:

public static void main(String[] args) {
  ForkJoinPool pool = new ForkJoinPool();
  pool.submit(new TaskA());

  // Properly shutdown your pool...
}

此示例在实现示例问题的一部分时非常简单。但一般来说,ForkJoin框架允许您创建树状结构的任务,其中每个父任务(例如A,B和P)允许您控制其直接子任务的执行。

答案 2 :(得分:5)

让我们从几个假设开始简化。

  1. 实际任务很少
  2. 它不是分布式环境
  3. 任务只有一个处理器
  4. 不是关键系统
  5. 表现不是必需的
  6. :)

    基于以上假设,我创建了https://gist.github.com/ankgupta/98148b8eead2fbbc2bbb

    interface Task{
        public void doTask();
    }
    
    ----EE.java
    
    import java.util.*;
    
    public class EE {
        private static EE ee = new EE();
        Map<String, Task> tasks = new HashMap<>();
        private EE(){ }
    
        public static EE getEE(){
            return ee;
        }
    
        public void register(String event, Task t){
            tasks.put(event, t);
        }
    
        public void message(String event){
            Task t = tasks.get(event);
            if(t!=null)
                t.doTask();
        }
    
    }
    
    class TaskA implements Task{
        public void doTask(){
            System.out.println("TaskA working!");
            EE.getEE().message("TASKA_PARTIAL");
            System.out.println("TaskA still working!");
            EE.getEE().message("TASKA_COMPLETE");
        }
    }
    
    
    class TaskB implements Task{
        public void doTask(){
            System.out.println("TaskB working!");
        }
    }
    
    
    class TaskC implements Task{
        public void doTask(){
            System.out.println("TaskC working!");
        }
    }
    
    
    public class Main{
        public static void main(String[] args){
            EE ee = EE.getEE();
            ee.register("STARTA", new TaskA());
            ee.register("TASKA_PARTIAL", new TaskB());
            ee.register("TASKA_COMPLETE", new TaskC());
            ee.message("STARTA");
        }
    }
    

    改善上述

    • 您应该有多个工作人员来添加并行的任务处理。
    • 如果任务失败,则进行错误处理,应该输入相应的消息 调用任务来处理它。
    • 您还应该使用真实的队列来处理事件。
    • 如果多个处理器用于同一任务,则使用任务分配算法。
    • 对于分布式环境,您必须处理命名,CAP ...

答案 3 :(得分:4)

在模式使用方面有两个选项,但实际上它们非常相似。在任何一种情况下,循环依赖性情况都需要作为任务依赖性配置中的错误来处理。例如A - &gt; B - &gt;一个

  1. 介体模式
  2. 在每个任务[i]完成后,它将通知调解员,调解员将通知任务[i]的所有后继任务。当执行引擎作为Mediator要使用的数据结构启动时,将读取任务依赖关系图。

    1. 通过消息总线发布/订阅模式。
    2. 任务依赖关系图将在引擎启动时被读取,并且每个任务将在主题[i]

      上订阅其前任任务的MessageBus(task [i])完成消息

      当每个任务[i]完成时,它将向主题[i]中的MessageBus发送完成消息。订阅主题[i]的每个任务都将收到通知并开始工作。

答案 4 :(得分:4)

你的问题很有趣,因为有人可以使用这种设计模拟一个简单的神经网络。就答案而言,我希望将问题视为任务排序而不是多线程/并发问题,因为并发可以通过执行有序任务来实现。现在让我们尝试使用event driven programming来实现它,因为它允许很好的松耦合组件。所以,现在我们的设计本质上是反应性的,所以一旦完成我们就会担心发出相关任务的信号,让我们使用observer pattern here

你的任务既是可观察的,也是观察者,因为他们等待前任的通知并通知后继,这给了我们以下的构造。

// Task.java
public abstract class Task extends Observable implements Runnable, Observer {
    private final Mutex lock = new Mutex();
    private final String taskId;

    public String getTaskId() {
        return this.taskId;
    }

    private final Set<String> completedTasks;
    private final Set<String> shouldCompletedTasksBeforeStart;

    public Task(final String taskId) {
        this.taskId = taskId;
        this.completedTasks = new HashSet<>();
        this.shouldCompletedTasksBeforeStart = new HashSet<>();
    }

    @Override
    public void run() {
        while (true) {
            this.lock.getLock();
            if (this.completedTasks.equals(this.shouldCompletedTasksBeforeStart)) {
                doWork();
                setChanged();
                notifyObservers(this.taskId);
                // reset
                this.completedTasks.clear();
            }
            this.lock.freeLock();
            try {
                // just some sleep, you change to how it fits you
                Thread.sleep(1000);
            } catch (final InterruptedException e) {
                // TODO Auto-generated catch block
            }
        }
    }

    @Override
    public void update(final Observable observable, final Object arg) {
        this.lock.getLock();
        this.completedTasks.add((String) arg);
        this.lock.freeLock();
    }

    public void addPredecessorTask(final Task task) {
        if (this.taskId.equals(task.taskId)) {
            return;
        }
        this.lock.getLock();
        // Notice here, it is a little logic make your predecessor/successor work
        task.addObserver(this);
        this.shouldCompletedTasksBeforeStart.add(task.taskId);
        this.lock.freeLock();
    }

    protected abstract void doWork();

}

//HelloTask.java
public static class HelloTask extends Task {
    public HelloTask(final String taskId) {
        super(taskId);
    }

    @Override
    protected void doWork() {
        System.out.println("Hello from " + getTaskId() + "!");
    }
}

//Main.java
public class Main {
    public static void main(final String[] args) {
        final HelloTask helloTaskA = new HelloTask("A");
        final HelloTask helloTaskB = new HelloTask("B");
        final HelloTask helloTaskC = new HelloTask("C");

        helloTaskA.addPredecessorTask(helloTaskB);
        helloTaskC.addPredecessorTask(helloTaskB);

        final ExecutorService pool = Executors.newFixedThreadPool(10);
        pool.execute(helloTaskC);
        pool.execute(helloTaskA);
        pool.execute(helloTaskB);

    }
}

实施是非常基础的,你可以改进它,但它为你提供了基础结构。知道你在哪里应用它会很有趣吗?

答案 5 :(得分:3)

如果我很了解您的需求,您可以使用像Activity这样的工作流引擎来解决它。 我认为这比根据您的特定需求重新创建工作流引擎更容易。

答案 6 :(得分:3)

如果你想重新发明轮子并自己开发解决方案,那很好 - 这是你的选择。但是,正确地执行此操作相当困难,尤其是线程部分。但是,如果您可以考虑至少使用构建基块的一些外部帮助,那么可以是:

  • guava's ListenableFuture - 使用此库,您可以创建Callable并将其提供给特殊线程池执行程序,然后允许ListenableFuture完成自定义回调。
  • 你可以看看RX Java Observable,它允许混合和匹配不同的任务。这使用非命令式编码风格,所以要小心!

答案 7 :(得分:3)

您的问题似乎是观察者模式的修改版本。 下面的解决方案更为通用,因为它允许继续依赖任务列表。

创建一个类Task,如下所示:

class Task{
List<Task> partialCompletionSuccessors;//List of tasks dependent on the partial completeion of this Task
List<Task> fullCompletetionSuccessors;//List of tasks dependent on the full completeion of this Task

List<Task> partialCompletionPredeccessor;//List of tasks that this task depends on their partial completion to start
List<Task> fullCompletetionPredeccessor;//List of tasks that this task depends on their full completion to start


private void notifySuccessorsOfPartialCompletion(){
    for(Task task: partialCompletionSuccessors){
           task.notifyOfPartialCompletion(this);
    }

}

private void notifySuccessorsOfFullCompletion(){
    for(Task task: fullCompletetionSuccessors){
           task.notifyOfPartialCompletion(this);
    }

}

private tryToProcceed(){
 if(partialCompletionPredeccessor.size() == 0 && fullCompletetionPredeccessor.size()==0 ){
     //Start the following task...
      ....
     //When this task partially completes
    notifySuccessorsOfPartialCompletion();

      //When this task fully completes
      notifySuccessorsOfFullCompletion();

  }

}


public void notifyOfPartialCompletion(Task task){// A method to notify the following task that a predeccessor task has partially completed
      partialCompletionPredeccessor.remove(task);
      tryToProcceed();

}

public void notifyOfFullCompletion(Task task){// A method to notify the following task that a predeccessor task has partially completed
      fullCompletetionPredeccessor.remove(task);
      tryToProcceed();

}

}

答案 8 :(得分:1)

这个问题很重要。我可以在您的设计中看到至少三个不同的子系统:

  • 任务DAG(对我来说部分完成只意味着你实际上有2个节点而不是1个),例如可以保存在数据库中;
  • 工作队列,其中下一个要执行的任务必须排队(这可能是某种消息队列,同样是持久性/事务性);
  • 实际的执行框架,可能涉及访问服务/数据库等外部资源,并且可能会被分发。

您的描述非常高,因此您可以开始设计这三个部分所需的抽象以及它们如何相互交互(就接口而言)。

一旦掌握了所有这些,就可以开始提供一些简单的实现。我将从一个&#34;本地模式&#34;开始,使用一个简单的内存DAG,一个阻塞队列和一些类型的java执行器。

您的问题未提供有关SLA,作业长度,失败/重试政策,交易等的详细信息,因此很难说明您的模块应如何实施。但我建议从高级抽象的角度思考并迭代实现。伟大的代码永远不会修复糟糕的设计。

如果需要,您可以停在那里,或者开始用第三方产品替换每个实施。

答案 9 :(得分:1)

有一个专门用于此目的的框架,称为Dexecutor,使用Dexecutor,您可以根据图形来模拟您的需求,在执行时,dexecutor将负责以可靠的方式执行它。

例如:

@Test
public void testDependentTaskExecution() {

    ExecutorService executorService = newExecutor();
    ExecutionEngine<Integer, Integer> executionEngine = new DefaultExecutionEngine<>(executorService);

    try {
        DefaultDependentTasksExecutor<Integer, Integer> executor = new DefaultDependentTasksExecutor<Integer, Integer>(
                executionEngine, new SleepyTaskProvider());

        executor.addDependency(1, 2);
        executor.addDependency(1, 2);
        executor.addDependency(1, 3);
        executor.addDependency(3, 4);
        executor.addDependency(3, 5);
        executor.addDependency(3, 6);
        executor.addDependency(2, 7);
        executor.addDependency(2, 9);
        executor.addDependency(2, 8);
        executor.addDependency(9, 10);
        executor.addDependency(12, 13);
        executor.addDependency(13, 4);
        executor.addDependency(13, 14);
        executor.addIndependent(11);

        executor.execute(ExecutionConfig.NON_TERMINATING);

        Collection<Node<Integer, Integer>> processedNodesOrder = Deencapsulation.getField(executor, "processedNodes");
        assertThat(processedNodesOrder).containsAll(executionOrderExpectedResult());
        assertThat(processedNodesOrder).size().isEqualTo(14);

    } finally {
        try {
            executorService.shutdownNow();
            executorService.awaitTermination(1, TimeUnit.SECONDS);
        } catch (InterruptedException e) {

        }
    }
}

private Collection<Node<Integer, Integer>> executionOrderExpectedResult() {
    List<Node<Integer, Integer>> result = new ArrayList<Node<Integer, Integer>>();
    result.add(new Node<Integer, Integer>(1));
    result.add(new Node<Integer, Integer>(2));
    result.add(new Node<Integer, Integer>(7));
    result.add(new Node<Integer, Integer>(9));
    result.add(new Node<Integer, Integer>(10));
    result.add(new Node<Integer, Integer>(8));
    result.add(new Node<Integer, Integer>(11));
    result.add(new Node<Integer, Integer>(12));
    result.add(new Node<Integer, Integer>(3));
    result.add(new Node<Integer, Integer>(13));
    result.add(new Node<Integer, Integer>(5));
    result.add(new Node<Integer, Integer>(6));
    result.add(new Node<Integer, Integer>(4));
    result.add(new Node<Integer, Integer>(14));
    return result;
}

private ExecutorService newExecutor() {
    return Executors.newFixedThreadPool(ThreadPoolUtil.ioIntesivePoolSize());
}

private static class SleepyTaskProvider implements TaskProvider<Integer, Integer> {

    public Task<Integer, Integer> provideTask(final Integer id) {

        return new Task<Integer, Integer>() {

            private static final long serialVersionUID = 1L;

            public Integer execute() {
                if (id == 2) {
                    throw new IllegalArgumentException("Invalid task");
                }
                return id;
            }
        };
    }
}

这是建模图

dexecutor-graph.png

这意味着任务#1,12和11将并行运行,一旦其中一个完成其依赖任务就会启动,例如一旦任务#1完成,其依赖任务#2和#3将启动

答案 10 :(得分:0)

从多线程中提取

class TaskA{
    SimpleTask Y;
    SimpleTask Z;

    SimpleTask PJ;
    SimpleTask RJ;

    Run(){
        // do the partial job
        PJ.Run();
        Y.Run();
        // do the remaining job
        RJ.Run();
        Z.Run();
        // return;
    }
} 

class TaskB{
    TaskA P;
    SimpleTask J;

    Run(){
        // do the job
        J.Run();
        P.Run();
        // return;
    }
}