Deterministic assignment of tasks to threads using ExecutorService

孤者浪人 提交于 2020-01-02 06:07:13

问题


Given Executor service with a fixed pool of threads, is it possible to guarantee deterministic assignment of tasks to threads? More precisely, assume there are just two threads, namely pool-thread-0 and pool-thread-1 and there is a collection of 2 tasks to be executed. What I wish to achieve is that the former thread always executes the first one, while the latter handles the remaining one.

Here is an example:

public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService executorService = newFixedThreadPool(2,
            new ThreadFactoryBuilder().setNameFormat("pool-thread-%d").build());

    for (int i = 0; i < 5; i++) {
        List<Callable<Integer>> callables = ImmutableList.of(createCallable(1), createCallable(2));
        executorService.invokeAll(callables);

    }
}

public static Callable<Integer> createCallable(final int task) {
    return new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            currentThread().sleep(1000);
            System.out.println(Thread.currentThread().getName() + " executes task num: " + task);
            return task;
        }
    };
}

Exemplary output from my machine:

pool-thread-0 executes task num: 1
pool-thread-1 executes task num: 2

pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1

pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1

pool-thread-0 executes task num: 2
pool-thread-1 executes task num: 1

pool-thread-0 executes task num: 1
pool-thread-1 executes task num: 2

In a nutshell, I wish to ensure that pool-thread-0 always executes the first task. Any help will be greatly appreciated!


回答1:


ExecutorService is not designed to provide "Thread affinity" to its Callable/Runnable. One could argue "that's kind of the point", the API is there to let programmers deal with the work description (the Callable), and not the Thread handling at all.

Your design, as is, where "there is a random data generator associated with each thread" is not suited to ExecutorService, for three reasons I see:

  1. You can not control what threads will be created (or destroyed!) and when (what if one crashes ? The pool will recreate it but what random generator will it get ?). So we can not infer a reliable way of saying "this thread" has "this generator", much less "the second" thread has "this generator" because there may not even be a second thread (what if each task is so quick that they are treated faster than you dispatch them ?).

  2. You do not control what tasks will be executed when. Well... with a Executors.newFixedThreadPool, you do to the extense that they are dispatched in order of submission, but for all you know, the OS scheduler may give all priority to thread 1, that will end up doing all the work, and thread 2 will have done nothing at all (it can be any proportion in between).

  3. The only way you can pass a "data generator" to a thread is if you override the ThreadFactory of the executor service. Otherwise, you have no access to the thread instance (appart from the callable themselves while running). So to associate a particular generator to a particular thread, you'd have to know which thread number you are currently creating, which is easy if you're counting threads, but difficult if you're trying to know what is the Callable that this thread is intended to (see point 2).

So I'd strongly suggest that you define some other way of associating your work units with your data generators, because "Thread instance" in general is not reliable - at least not through Executor Service. E.g. when you say

I need to provide that the combinations of threads and data they process are repeatable.

I understand that you will dispatch always a certain number of Callables, and you need each of them to work on a specific set of data as issued by a specific generator. Say if we have a given number of tasks and 3 generators, task(N) will use generator N%3.

For the results to be repeatable, you further need that the tasks that use the same generator do not execute concurrently (what you seek to achieve with thread affinity ?).

There are a certain number of patterns that can achieve that.

1 is : refactor to a producer / consummer (do it the other way around)

Make 3 tasks in your executor service, each one listens to a BlockingQueue (its private waiting list) and has its own private generator. These are the consummers.
Make your main thread a producer : when it creates work unit (what used to be a Callable in your original design) number N, dispatch it to the waiting queue number N%3. That's it : each consummer will receive, in order and sequentially, its own data to compute, in the order you wish. You have achieved "affinity".

2 Is : make task dispatch tasks themselves. (do it the hacky way)

First, refactor your callables to have a link to the generator they need to use. Then, on your main thread, build a list of tasks that are to be run for each generator.
Dispatch, from the main thread, the first task for each generator.
And the end of each callable, make the Callable dispatch the next work unit from its list.
Be carreful not to "lock you out", though, if you dispatch callables from callables, do not wait for results, because this will prevent Callables from finishing, which in turn prevent the newly dispatched to execute. Which is a deadlock.

3 Is : same as 2 with less efficiency, but less risks

Instead of dispatching Callables from inside callables, dispatch only from your main thread, by waiting on the futures.

Doing either of these two ways, you are not guaranteed what tasks will finish first or last, but you are guaranteed that the unit of works you dispatch are predictably associated to a data generator you control, and that they will execute in the order you dispatch them. Which hopefully is enough.



来源:https://stackoverflow.com/questions/25048385/deterministic-assignment-of-tasks-to-threads-using-executorservice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!