464 votes

Comment attendre que tous les threads se terminent, en utilisant ExecutorService ?

J'ai besoin d'exécuter un certain nombre de tâches 4 à la fois, quelque chose comme ceci :

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
    taskExecutor.execute(new MyTask());
}
//...wait for completion somehow

Comment puis-je être averti lorsque tous les projets sont terminés ? Pour l'instant, je ne vois rien de mieux que de définir un compteur global de tâches et de le diminuer à la fin de chaque tâche, puis de surveiller en boucle infinie que ce compteur devienne 0 ; ou d'obtenir une liste de Futures et de surveiller en boucle infinie isDone pour chacun d'entre eux. Quelles sont les meilleures solutions n'impliquant pas de boucles infinies ?

Gracias.

524voto

cletus Points 276888

En gros, sur un ExecutorService vous appelez shutdown() y luego awaitTermination() :

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}
taskExecutor.shutdown();
try {
  taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
  ...
}

1 votes

C'est beau et simple, mais j'ai l'impression que la fermeture n'est pas utilisée pour ce qu'elle a été conçue (ou alors c'est bien ?).

2 votes

C'est un modèle commun. Les threadpools transitoires comme celui ci-dessus sont bons pour plusieurs raisons et je les préfère presque toujours aux threadpools persistants où beaucoup plus de choses peuvent mal tourner ou du moins être plus difficiles à comprendre. L'exemple ci-dessus est vraiment simple et c'est pourquoi je l'aime bien.

11 votes

C'est exactement ce à quoi servent shutdown / awaitTermination

202voto

ChssPly76 Points 53452

Utilisez un CountDownLatch :

CountDownLatch latch = new CountDownLatch(totalNumberOfTasks);
ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
while(...) {
  taskExecutor.execute(new MyTask());
}

try {
  latch.await();
} catch (InterruptedException E) {
   // handle
}

et à l'intérieur de votre tâche (entouré de try / finally)

latch.countDown();

5 votes

Il n'y a pas 4 tâches. Il y a "un certain nombre de tâches" effectuées 4 par 4.

0 votes

Seulement, au lieu de CountDownLatch(4), je dois utiliser le nombre total de fils créés, n'est-ce pas ?

2 votes

Désolé, j'ai mal compris la question. Oui, le nombre de tâches devrait être l'argument du constructeur de CountDownLatch.

104voto

sjlee Points 3457

ExecutorService.invokeAll() le fait pour vous.

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
List<Callable<?>> tasks; // your tasks
// invokeAll() returns when all tasks are complete
List<Future<?>> futures = taskExecutor.invokeAll(tasks);

0 votes

La difficulté vient si/quand vous devez commencer les "4" fils un par un, pièce par pièce, puis joindre/finir les 4...

0 votes

@rogerdpack : Je suis encore en train d'apprendre les exécuteurs et tout ça. Mais en réponse à ce que vous demandez. Est-ce que les 4 threads à la fois ne devraient pas faire partie d'une tâche batch qui est exécutée en utilisant la réponse ci-dessus ?

3 votes

Cette méthode ne fonctionne que si vous connaissez à l'avance le nombre de tâches à accomplir.

55voto

rogerdpack Points 12806

Vous pouvez également utiliser les Listes de Futures :

List<Future> futures = new ArrayList<Future>();
// now add to it:
futures.add(executorInstance.submit(new Callable<Void>() {
  public Void call() throws IOException {
     // do something
    return null;
  }
}));

ensuite, lorsque vous voulez faire une jointure sur tous ces threads, c'est essentiellement l'équivalent d'une jointure sur chacun d'entre eux (avec l'avantage supplémentaire que les exceptions des threads enfants sont réactivées dans le thread principal) :

for(Future f: this.futures) { f.get(); }

En fait, l'astuce consiste à appeler .get() sur chaque futur, un par un, au lieu de faire une boucle infinie en appelant isDone() sur (tous ou chacun). Ainsi, vous êtes assuré de "passer" à travers et au-delà de ce bloc dès que le dernier thread se termine. Le problème est que, puisque l'appel .get() relance les exceptions, si l'un des threads meurt, vous relèverez l'exception avant que les autres threads n'aient terminé. catch ExecutionException autour de l'appel]. L'autre problème est qu'il garde une référence à tous les threads, donc s'ils ont des variables locales de thread, elles ne seront pas collectées avant que vous ayez passé ce bloc (bien que vous puissiez contourner cela, si cela devenait un problème, en retirant les Future de la ArrayList). Si vous voulez savoir quel Future "finit en premier", vous pouvez utiliser quelque chose comme https://stackoverflow.com/a/31885029/32453

3 votes

Pour savoir lequel "finit en premier", utilisez ExecutorCompletionService.take : stackoverflow.com/a/11872604/199364

30voto

stryba Points 1423

Juste mes deux centimes. Pour surmonter l'exigence de CountDownLatch pour connaître le nombre de tâches à l'avance, vous pourriez le faire à l'ancienne en utilisant un simple Semaphore .

ExecutorService taskExecutor = Executors.newFixedThreadPool(4);
int numberOfTasks=0;
Semaphore s=new Semaphore(0);
while(...) {
    taskExecutor.execute(new MyTask());
    numberOfTasks++;
}

try {
    s.aquire(numberOfTasks);
...

Dans votre tâche, il suffit d'appeler s.release() comme vous le feriez latch.countDown();

0 votes

En voyant cela, je me suis d'abord demandé si ce serait un problème si certains release se produisent avant que les appels acquire mais après avoir lu la documentation de Semaphore, je vois que c'est correct.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X