ForkJoinPool: вызывает ли invokeall() неправильный порядок?

Насколько я понимаю, ForkJoinPool использует принцип «последним пришел — первым ушел» (LiFo). Возможно, лучшее объяснение, которое я смог найти, это эта ссылка. Однако метод invokeall() в ForkJoinPool.java, по-видимому, объединяет (quietlyjoin()?) задачи в том порядке, в котором они были отправлены (FiFo), см. код ниже. Не лучше ли сделать наоборот, ведь вся обработка LiFo?

Не приведет ли это к меньшему количеству «компенсационных потоков»? Я думаю переписать свой код для отправки задач, а не использовать invokeall(), и из-за этого присоединиться к ним на основе LiFo. Смотрите также:

ForkJoinPool останавливается во время invokeAll/join

ForkJoinPool, похоже, теряет поток

РЕДАКТИРОВАТЬ: существует разница в том, отправляется ли задача во внешнюю очередь или в очередь исполнителя. Я имею в виду деку рабочего. Я предполагаю, что invokeall() может привести к остановке потока здесь.

public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
        // In previous versions of this class, this method constructed
        // a task to run ForkJoinTask.invokeAll, but now external
        // invocation of multiple tasks is at least as efficient.
        ArrayList<Future<T>> futures = new ArrayList<>(tasks.size());

        try {
            for (Callable<T> t : tasks) {
                ForkJoinTask<T> f = new ForkJoinTask.AdaptedCallable<T>(t);
                futures.add(f);
                externalSubmit(f);
            }
            for (int i = 0, size = futures.size(); i < size; i++)
                ((ForkJoinTask<?>)futures.get(i)).quietlyJoin();
            return futures;
        } catch (Throwable t) {
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(false);
            throw t;
        }
    }

person simon    schedule 06.04.2020    source источник
comment
Я думаю, что «принцип «последним пришел — первым вышел» (LiFo)» действителен только для ForkJoinTasks и подзадач, отправленных через ForkJoinTask.invokeAll() (потому что именно здесь происходит рекурсивное разделение (также известное как fork/join), приводящее к поведению LIFO). При использовании ForkJoinPool.invokeAll() между задачами нет зависимостей, поэтому объединение по порядку не имеет большого значения по сравнению с объединением их в обратном порядке.   -  person Thomas Kläger    schedule 06.04.2020
comment
Что, если ваш основной поток вызывает все 5 задач, и все они создают 3 подзадачи. Первая обрабатываемая задача — это последняя подзадача последней (5-й) основной задачи. Правильный? Однако, поскольку первый join() в основном потоке связан с 1-й «задачей» из-за приведенного выше кода invokeall(), поток останавливается.... что приводит к накладным расходам, поскольку необходимо инициировать «компенсационный поток». Хотя, это моя интерпретация теории, которую я прочитал до сих пор.   -  person simon    schedule 06.04.2020
comment
Одна вещь — основной поток: это не один из потоков ForkJoinPools, поэтому он остановится до тех пор, пока не будут выполнены все 5 задач, независимо от того, в каком порядке они выполняются. 5 задач выполняются параллельно в рабочих потоках из ForkJoinPool, порождая по 3 подзадачи в каждой. При попытке присоединиться к подзадачам рабочие потоки не останавливаются, а выполняют другую работу из своих рабочих очередей (каждая из которых содержит 3 подзадачи).   -  person Thomas Kläger    schedule 06.04.2020
comment
Вы правы, есть que и deque. Что, если работники ForkJoinPool инициируют 3 подзадачи с помощью invokeall()? В этом случае 3 новые задачи помещаются в очередь, где рабочий начнет с последней подзадачи. После этого поток (не рабочий) останавливается, потому что метод invoke all() ожидает присоединения к первой задаче. изменение этого порядка «присоединения» может позволить рабочему выполнять все вычисления в 1 потоке. Это правильно?   -  person simon    schedule 06.04.2020
comment
Даже в этом случае поток не останавливается, потому что invokeAll() не делает Thread.join() (что остановит). Вместо этого он решает: если текущий поток является внешним потоком (например, основным потоком), он ожидает завершения. Если запущенный поток является ForkJoinWorkerThread, он попытается выполнить другую работу (либо из своей собственной рабочей очереди, либо путем кражи работы из других очередей).   -  person Thomas Kläger    schedule 06.04.2020