Ensuring task execution order in threadpool

后端 未结 17 935
情深已故
情深已故 2020-12-12 11:54

I have been reading about the thread-pool pattern and I can\'t seem to find the usual solution for the following problem.

I sometimes want tasks to be executed serial

相关标签:
17条回答
  • 2020-12-12 12:34

    Use two Active Objects. In two words: active object pattern consists from priority queue and 1 or many working threads those can get tasks from queue and process its.

    So use one active object with one working thread: all tasks those would be places to queue would be processed sequentially. Use second active object with number of working thread more then 1. In this case working threads would get and process tasks from queue in any order.

    Luck.

    0 讨论(0)
  • 2020-12-12 12:38

    To do what you want to do with a threadpool, you might have to create some kind of scheduler.

    Something like that:

    TaskQueue -> Scheduler -> Queue -> ThreadPool

    Scheduler runs in its own thread, keeping tracks of dependencies between jobs. When a job is ready to be done, the scheduler just pushes it in the queue for the threadpool.

    The ThreadPool might have to send signals to the Scheduler to indicate when a job is done so the scheduler can put jobs depending on that job into the Queue.

    In your case, the dependencies could probably be stored in a linked list.

    Let's say you have the following dependencies: 3 -> 4 -> 6 -> 8

    Job 3 is running on the threadpool, you still have no ideas that job 8 exists.

    Job 3 ends. You remove the 3 from the linked list, you put job 4 on the queue to the threadpool.

    Job 8 arrives. You put it at the end of the linked list.

    The only constructs that have to be fully synchronized are the Queues before and after the scheduler.

    0 讨论(0)
  • 2020-12-12 12:38

    How would you ensure those tasks are ordered?

    push task1
    push task2
    push task346
    push task5
    

    In response to the edit:

    push task1
    push task27   **
    push task3468   *
    push task5
    push task9
    
    0 讨论(0)
  • 2020-12-12 12:39

    Thread Pool with ordered and unordered execute methods:

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class OrderedExecutor {
        private ExecutorService multiThreadExecutor;
        // for single Thread Executor
        private ThreadLocal<ExecutorService> threadLocal = new ThreadLocal<>();
    
        public OrderedExecutor(int nThreads) {
            this.multiThreadExecutor = Executors.newFixedThreadPool(nThreads);
        }
    
        public void executeUnordered(Runnable task) {
            multiThreadExecutor.submit(task);
        }
    
        public void executeOrdered(Runnable task) {
            multiThreadExecutor.submit(() -> {
                ExecutorService singleThreadExecutor = threadLocal.get();
                if (singleThreadExecutor == null) {
                    singleThreadExecutor = Executors.newSingleThreadExecutor();
                    threadLocal.set(singleThreadExecutor);
                }
                singleThreadExecutor.submit(task);
            });
        }
    
        public void clearThreadLocal() {
            threadLocal.remove();
        }
    
    }
    

    After filling all queues the threadLocal should be cleared. The only drawback is that singleThreadExecutor will be created each time the method

    executeOrdered(Runnable task)

    invoked in separate thread

    0 讨论(0)
  • 2020-12-12 12:41

    This is achievable, well, as far as I understand your scenario. Basically what you need is do something smart to coordinate your tasks in main thread. Java API your need are ExecutorCompletionService and Callable

    First, implement your callable task:

    public interface MyAsyncTask extends Callable<MyAsyncTask> {
      // tells if I am a normal or dependent task
      private boolean isDependent;
    
      public MyAsyncTask call() {
        // do your job here.
        return this;
      }
    }
    

    Then in your main thread, use CompletionService coordinate the dependent task execution (i.e. a wait mechanism):

    ExecutorCompletionService<MyAsyncTask> completionExecutor = new 
      ExecutorCompletionService<MyAsyncTask>(Executors.newFixedThreadPool(5));
    Future<MyAsyncTask> dependentFutureTask = null;
    for (MyAsyncTask task : tasks) {
      if (task.isNormal()) {
        // if it is a normal task, submit it immediately.
        completionExecutor.submit(task);
      } else {
        if (dependentFutureTask == null) {
          // submit the first dependent task, get a reference 
          // of this dependent task for later use.
          dependentFutureTask = completionExecutor.submit(task);
        } else {
          // wait for last one completed, before submit a new one.
          dependentFutureTask.get();
          dependentFutureTask = completionExecutor.submit(task);
        }
      }
    }
    

    By doing this, you use a single executor (threadpool size 5) execute both normal and dependent tasks, the normal task are executed immediately as soon as submitted, the dependent tasks are executed one by one (wait are performed in main thread by calling get() on Future before submitting new dependent task), so at any point of time, you always have a number of normal tasks and a single dependent task (if exists) running in a single threadpool.

    This is just a head start, by using ExecutorCompletionService, FutureTask and Semaphore, you can implement more complex thread coordination scenario.

    0 讨论(0)
  • 2020-12-12 12:42

    Basically, there are a number of pending tasks. Some of the tasks can only be performed when one or more other pending tasks have finished executing.

    The pending tasks can be modeled in a dependency graph:

    • "task 1 -> task2" means "task 2 can be executed only after task 1 is finished." the arrows point in the direction of execution order.
    • the indegree of a task (the number of tasks pointing to it) determines whether the task is ready for execution. If the indegree is 0, it can be executed.
    • sometimes a task must wait for multiple tasks to finish, the indegree is then >1.
    • if a task doesn't have to wait for other tasks to finish anymore (its indegree is zero), it can be submitted to the thread pool with worker threads, or the queue with tasks waiting to be picked up by a worker thread. You know the submitted task will not cause deadlock, because the task isn't waiting for anything. As an optimization, you can use a priority queue, e.g. in which tasks that more tasks in the dependency graph depend on will be executed first. This also can't provoke deadlock, because all tasks in the thread pool can be executed. It can provoke starvation, however.
    • If a task finishes execution, it can be removed from the dependency graph, possibly reducing the indegree of other tasks, which can in turn be submitted to the pool of working threads.

    So there is (at least) one thread used to add/remove pending tasks, and there is a thread pool of working threads.

    When a task is added to the dependency graph, you must check:

    • how the task is connected in the dependency graph: what tasks must it wait for to finish and what tasks must wait for it to finish? Draw connections from and to the new task accordingly.
    • once the connections are drawn: did the new connections cause any cycles in the dependency graph? If so, there is a deadlock situation.

    Performance:

    • this pattern is slower than sequential execution if parallel execution is in fact rarely possible, because you need extra administration to do everything almost sequentially anyway.
    • this pattern is fast if many tasks can be performed simultaneously in practice.

    Assumptions:

    As you may have read between the lines, you must design the tasks so that they don't interfere with other tasks. Also, there must be a way to determine the priority of the tasks. The task priority should include the data handled by each task. Two tasks may not alter the same object simultaneously; one of the tasks should get priority over the other one instead, or the performed operations on the object must be thread-safe.

    0 讨论(0)
提交回复
热议问题