ExecutorService, comment attendre la fin de toutes les tâches

198

Quelle est la manière la plus simple d'attendre que toutes les tâches ExecutorServicese terminent? Ma tâche est principalement informatique, donc je veux juste exécuter un grand nombre de travaux - un sur chaque cœur. En ce moment, ma configuration ressemble à ceci:

ExecutorService es = Executors.newFixedThreadPool(2);
for (DataTable singleTable : uniquePhrases) {   
    es.execute(new ComputeDTask(singleTable));
}
try{
    es.wait();
} 
catch (InterruptedException e){
    e.printStackTrace();
}

ComputeDTaskimplémente runnable. Cela semble exécuter correctement les tâches, mais le code se bloque wait()avec IllegalMonitorStateException. C'est étrange, parce que j'ai joué avec des exemples de jouets et que cela semblait fonctionner.

uniquePhrasescontient plusieurs dizaines de milliers d'éléments. Dois-je utiliser une autre méthode? Je cherche quelque chose d'aussi simple que possible

george smiley
la source
1
si vous vouliez utiliser wait (les réponses vous disent que vous ne le faites pas): vous devez toujours synchroniser sur l'objet (dans ce cas es) quand vous voulez l'attendre - le verrou sera automatiquement libéré en attendant
mihi
13
une meilleure façon d'initialiser le ThreadPool estExecutors.newFixedThreadPool(System.getRuntime().availableProcessors());
7
Runtime.getRuntime (). AvailableProcessors ();
Vik Gamov
[Ce] [1] est une alternative intéressante .. [1]: stackoverflow.com/questions/1250643/…
Răzvan Petruescu
1
si vous souhaitez utiliser CountDownLatch, voici un exemple de code: stackoverflow.com/a/44127101/4069305
Tuan Pham

Réponses:

213

L'approche la plus simple consiste à utiliser ExecutorService.invokeAll()ce qui fait ce que vous voulez dans une doublure. Dans votre langage, vous devrez modifier ou encapsuler ComputeDTaskpour implémenter Callable<>, ce qui peut vous donner un peu plus de flexibilité. Probablement, dans votre application, il existe une implémentation significative de Callable.call(), mais voici un moyen de l'envelopper si vous ne l'utilisez pas Executors.callable().

ExecutorService es = Executors.newFixedThreadPool(2);
List<Callable<Object>> todo = new ArrayList<Callable<Object>>(singleTable.size());

for (DataTable singleTable: uniquePhrases) { 
    todo.add(Executors.callable(new ComputeDTask(singleTable))); 
}

List<Future<Object>> answers = es.invokeAll(todo);

Comme d'autres l'ont souligné, vous pouvez utiliser la version timeout de invokeAll()si nécessaire. Dans cet exemple, answersva contenir un tas de Futures qui renverra des valeurs nulles (voir la définition de Executors.callable(). Probablement ce que vous voulez faire est une légère refactorisation afin que vous puissiez obtenir une réponse utile ou une référence au sous-jacent ComputeDTask, mais je peux ne dis rien de ton exemple.

Si ce n'est pas clair, notez que invokeAll()cela ne reviendra pas tant que toutes les tâches ne seront pas terminées. (c'est-à-dire que tous les Futures de votre answerscollection seront .isDone()signalés si demandé.) Cela évite tout arrêt manuel, waitTermination, etc ... et vous permet de le réutiliser ExecutorServiceproprement pour plusieurs cycles, si vous le souhaitez.

Il y a quelques questions connexes sur SO:

Aucun de ces éléments n'est strictement pertinent pour votre question, mais ils fournissent un peu de couleur sur la façon dont les gens pensent Executor/ ExecutorServicedevraient être utilisés.

andersoj
la source
9
C'est parfait si vous ajoutez tous vos travaux dans un lot et que vous vous accrochez à la liste des Callables, mais cela ne fonctionnera pas si vous appelez ExecutorService.submit () dans une situation de rappel ou de boucle d'événement.
Desty
2
Je pense qu'il convient de mentionner que shutdown () doit toujours être appelé lorsque ExecutorService n'est plus nécessaire, sinon les threads ne se termineront jamais (sauf dans les cas où corePoolSize = 0 ou allowCoreThreadTimeOut = true).
John29
incroyable! Exactement ce que je cherchais. Merci beaucoup d'avoir partagé la réponse. Laissez-moi essayer ça.
MohamedSanaulla
59

Si vous souhaitez attendre la fin de toutes les tâches, utilisez la shutdownméthode au lieu de wait. Ensuite, suivez-le avec awaitTermination.

Vous pouvez également utiliser Runtime.availableProcessorspour obtenir le nombre de threads matériels afin d'initialiser correctement votre pool de threads.

NG.
la source
27
shutdown () empêche ExecutorService d'accepter de nouvelles tâches et ferme les threads de travail inactifs. Il n'est pas spécifié d'attendre la fin de l'arrêt et l'implémentation dans ThreadPoolExecutor n'attend pas.
Alain O'Dea
1
@Alain - merci. J'aurais dû mentionner waitTermination. Fixé.
NG.
5
Que faire si pour qu'une tâche se termine, elle doit planifier d'autres tâches? Par exemple, vous pouvez créer une traversée d'arbre multithread qui transfère les branches aux threads de travail. Dans ce cas, comme ExecutorService est arrêté instantanément, il n'accepte pas les travaux planifiés récursivement.
Brian Gordon
2
awaitTerminationnécessite un délai d'expiration comme paramètre. Bien qu'il soit possible de fournir un temps fini et de placer une boucle autour pour attendre que tous les threads soient terminés, je me demandais s'il y avait une solution plus élégante.
Abhishek S
1
Vous avez raison, mais voyez cette réponse - stackoverflow.com/a/1250655/263895 - vous pouvez toujours lui donner un délai incroyablement long
NG.
48

Si attendre que toutes les tâches soient ExecutorServiceterminées n'est pas précisément votre objectif, mais plutôt attendre qu'un lot spécifique de tâches soit terminé, vous pouvez utiliser un CompletionService- spécifiquement, un ExecutorCompletionService.

L'idée est de créer une ExecutorCompletionServiceenveloppe Executor, de soumettre un certain nombre de tâches connues via le CompletionService, puis de tirer le même nombre de résultats de la file d'attente d'achèvement en utilisant take()(quels blocs) ou poll()(qui ne le fait pas). Une fois que vous avez dessiné tous les résultats attendus correspondant aux tâches que vous avez soumises, vous savez que tout est terminé.

Permettez-moi de le répéter une fois de plus, car ce n'est pas évident à partir de l'interface: vous devez savoir combien de choses vous mettez dans le CompletionServiceafin de savoir combien de choses essayer de tirer. Cela est particulièrement important avec la take()méthode: appelez-la une fois de plus et cela bloquera votre thread appelant jusqu'à ce qu'un autre thread soumette un autre travail à la même chose CompletionService.

Il y a quelques exemples montrant comment utiliserCompletionService dans le livre Java Concurrency in Practice .

seh
la source
C'est un bon contrepoint à ma réponse - je dirais que la réponse simple à la question est invokeAll (); mais @seh a raison lorsqu'il soumet des groupes d'emplois à l'ES et attend qu'ils se terminent ... --JA
andersoj
@ om-nom-nom, merci d'avoir mis à jour les liens. Je suis heureux de voir que la réponse est toujours utile.
seh
1
Bonne réponse, je n'étais pas au courantCompletionService
Vic
1
Il s'agit de l'approche à utiliser si vous ne voulez pas arrêter un ExecutorService existant, mais souhaitez simplement soumettre un lot de tâches et savoir quand elles sont toutes terminées.
ToolmakerSteve
11

Si vous voulez attendre la fin de l'exécution du service exécuteur, appelez shutdown()puis attendez Termination (unités, type d'unité) , par exemple awaitTermination(1, MINUTE). ExecutorService ne bloque pas sur son propre moniteur, vous ne pouvez donc pas utiliser waitetc.

mdma
la source
Je pense que c'est waitTermination.
NG.
@SB - Merci - je vois que ma mémoire est faillible! J'ai mis à jour le nom et ajouté un lien pour être sûr.
mdma
Pour attendre «pour toujours», utilisez-le comme awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); stackoverflow.com/a/1250655/32453
rogerdpack
Je pense que c'est l'approche la plus simple
Shervin Asgari
1
@MosheElisha, êtes-vous sûr ?. docs.oracle.com/javase/8/docs/api/java/util/concurrent/… indique Initie un arrêt ordonné dans lequel les tâches soumises précédemment sont exécutées, mais aucune nouvelle tâche ne sera acceptée.
Jaime Hablutzel
7

Vous pouvez attendre la fin des travaux à un certain intervalle:

int maxSecondsPerComputeDTask = 20;
try {
    while (!es.awaitTermination(uniquePhrases.size() * maxSecondsPerComputeDTask, TimeUnit.SECONDS)) {
        // consider giving up with a 'break' statement under certain conditions
    }
} catch (InterruptedException e) {
    throw new RuntimeException(e);    
}

Ou vous pouvez utiliser ExecutorService . soumettre ( Runnable ) et collecter les objets Future qu'il retourne et appeler get () sur chacun à son tour pour attendre qu'ils se terminent.

ExecutorService es = Executors.newFixedThreadPool(2);
Collection<Future<?>> futures = new LinkedList<<Future<?>>();
for (DataTable singleTable : uniquePhrases) {
    futures.add(es.submit(new ComputeDTask(singleTable)));
}
for (Future<?> future : futures) {
   try {
       future.get();
   } catch (InterruptedException e) {
       throw new RuntimeException(e);
   } catch (ExecutionException e) {
       throw new RuntimeException(e);
   }
}

InterruptedException est extrêmement important à gérer correctement. C'est ce qui vous permet, à vous ou aux utilisateurs de votre bibliothèque, de terminer un long processus en toute sécurité.

Alain O'Dea
la source
6

Utilisez simplement

latch = new CountDownLatch(noThreads)

Dans chaque fil

latch.countDown();

et comme barrière

latch.await();
J. Ruhe
la source
6

Cause racine de IllegalMonitorStateException :

Lancé pour indiquer qu'un thread a tenté d'attendre sur le moniteur d'un objet ou d'avertir les autres threads en attente sur le moniteur d'un objet sans posséder le moniteur spécifié.

À partir de votre code, vous venez d'appeler wait () sur ExecutorService sans posséder le verrou.

Le code ci-dessous corrigera IllegalMonitorStateException

try 
{
    synchronized(es){
        es.wait(); // Add some condition before you call wait()
    }
} 

Suivez l'une des approches ci-dessous pour attendre la fin de toutes les tâches qui ont été soumises ExecutorService.

  1. Itérer à travers toutes les Futuretâches à partir submitde ExecutorServiceet vérifier l'état avec blocage de l'appel get()sur l' Futureobjet

  2. Utiliser invokeAll surExecutorService

  3. Utilisation de CountDownLatch

  4. Utiliser ForkJoinPool ou newWorkStealingPool de Executors(depuis java 8)

  5. Arrêtez le pool comme recommandé dans la page de documentation d'Oracle

    void shutdownAndAwaitTermination(ExecutorService pool) {
       pool.shutdown(); // Disable new tasks from being submitted
       try {
       // Wait a while for existing tasks to terminate
       if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
           pool.shutdownNow(); // Cancel currently executing tasks
           // Wait a while for tasks to respond to being cancelled
           if (!pool.awaitTermination(60, TimeUnit.SECONDS))
           System.err.println("Pool did not terminate");
       }
    } catch (InterruptedException ie) {
         // (Re-)Cancel if current thread also interrupted
         pool.shutdownNow();
         // Preserve interrupt status
         Thread.currentThread().interrupt();
    }

    Si vous souhaitez attendre gracieusement la fin de toutes les tâches lorsque vous utilisez l'option 5 au lieu des options 1 à 4, modifiez

    if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {

    à

    un while(condition)qui vérifie toutes les 1 minute.

Ravindra babu
la source
6

Vous pouvez utiliser la ExecutorService.invokeAllméthode, il exécutera toutes les tâches et attendra que tous les threads aient terminé leur tâche.

Voici javadoc complet

Vous pouvez également utiliser une version surchargée de cette méthode pour spécifier le délai d'expiration.

Voici un exemple de code avec ExecutorService.invokeAll

public class Test {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService service = Executors.newFixedThreadPool(3);
        List<Callable<String>> taskList = new ArrayList<>();
        taskList.add(new Task1());
        taskList.add(new Task2());
        List<Future<String>> results = service.invokeAll(taskList);
        for (Future<String> f : results) {
            System.out.println(f.get());
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(2000);
            return "Task 1 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task1";
        }
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        try {
            Thread.sleep(3000);
            return "Task 2 done";
        } catch (Exception e) {
            e.printStackTrace();
            return " error in task2";
        }
    }
}
Nitin Vavdiya
la source
3

J'ai également la situation que j'ai un ensemble de documents à explorer. Je commence par un document initial "initial" qui doit être traité, ce document contient des liens vers d'autres documents qui doivent également être traités, etc.

Dans mon programme principal, je veux juste écrire quelque chose comme le suivant, où Crawlercontrôle un tas de threads.

Crawler c = new Crawler();
c.schedule(seedDocument); 
c.waitUntilCompletion()

La même situation se produirait si je voulais naviguer dans un arbre; je ferais apparaître le nœud racine, le processeur de chaque nœud ajouterait des enfants à la file d'attente si nécessaire, et un tas de threads traiterait tous les nœuds de l'arbre, jusqu'à ce qu'il n'y en ait plus.

Je n'ai rien trouvé dans la JVM que j'ai trouvé un peu surprenant. J'ai donc écrit une classe ThreadPoolque l'on peut utiliser directement ou sous-classe pour ajouter des méthodes adaptées au domaine, par exemple schedule(Document). J'espère que ça aide!

ThreadPool Javadoc | Maven

Adrian Smith
la source
Doc Link est mort
Manti_Core
@Manti_Core - merci, mis à jour.
Adrian Smith
2

Ajoutez tous les threads de la collection et soumettez-le à l'aide de invokeAll. Si vous pouvez utiliser la invokeAllméthode de ExecutorService, JVM ne passera pas à la ligne suivante tant que tous les threads ne seront pas terminés.

Voici un bon exemple: invokeAll via ExecutorService

zgormez
la source
1

Soumettez vos tâches dans le Runner , puis attendez d'appeler la méthode waitTillDone () comme ceci:

Runner runner = Runner.runner(2);

for (DataTable singleTable : uniquePhrases) {

    runner.run(new ComputeDTask(singleTable));
}

// blocks until all tasks are finished (or failed)
runner.waitTillDone();

runner.shutdown();

Pour l'utiliser, ajoutez cette dépendance gradle / maven: 'com.github.matejtymes:javafixes:1.0'

Pour plus de détails, regardez ici: https://github.com/MatejTymes/JavaFixes ou ici: http://matejtymes.blogspot.com/2016/04/executor-that-notifies-you-when-task.html

Matej Tymes
la source
0

Une alternative simple à cela est d'utiliser des threads avec join. Reportez-vous à: Rejoindre des discussions

Vicky Kapadia
la source
3
ExecutorServices simplifie les choses
David Mann
0

J'attendrai simplement que l'exécuteur se termine avec un délai spécifié que vous pensez qu'il convient aux tâches à accomplir.

 try {  
         //do stuff here 
         exe.execute(thread);
    } finally {
        exe.shutdown();
    }
    boolean result = exe.awaitTermination(4, TimeUnit.HOURS);
    if (!result)

    {
        LOGGER.error("It took more than 4 hour for the executor to stop, this shouldn't be the normal behaviour.");
    }
punkers
la source
0

On dirait que vous avez besoin ForkJoinPoolet utilisez le pool global pour exécuter des tâches.

public static void main(String[] args) {
    // the default `commonPool` should be sufficient for many cases.
    ForkJoinPool pool = ForkJoinPool.commonPool(); 
    // The root of your task that may spawn other tasks. 
    // Make sure it submits the additional tasks to the same executor that it is in.
    Runnable rootTask = new YourTask(pool); 
    pool.execute(rootTask);
    pool.awaitQuiescence(...);
    // that's it.
}

La beauté réside dans le fait pool.awaitQuiescenceque la méthode bloquera l'utilisation du thread de l'appelant pour exécuter ses tâches, puis reviendra lorsqu'elle sera vraiment vide.

adib
la source