Traitement parallèle en Java; conseils nécessaires, c'est-à-dire sur les interfaces Runnanble/Callable


Supposons que j'ai un ensemble d'objets qui doivent être analysés de deux manières différentes, qui prennent relativement longtemps et impliquent des appels IO, j'essaie de comprendre comment/si je pouvais optimiser cette partie de mon logiciel, en particulier en utilisant les processeurs multiples (la machine sur laquelle je suis assis par exemple est un i7 à 8 cœurs qui ne dépasse presque jamais 10% de charge pendant l'exécution).

Je suis assez nouveau dans la programmation parallèle ou le multi-threading (je ne sais pas quel est le bon terme j'ai donc lu certaines des questions précédentes, en accordant une attention particulière aux réponses hautement votées et informatives. Je suis également en train de parcourir le tutoriel Oracle/Sun sur la concurrence.

Voici ce que j'ai pensé jusqu'à présent;

  • Une collection thread-safe contient les objets à analyser
  • Dès qu'il y a des objets dans la collection (ils proviennent un couple à la fois d'une série de requêtes), un thread par objet est démarré
  • Chacun fil spécifique prend en charge les préparations initiales de pré-analyse; puis appelle sur les analyses.
  • Les deux analyses sont implémentées en tant que Runnables/Callables, et donc appelées par le thread si nécessaire.

Et mes questions sont:

  1. Est - ce un schéma raisonnable, sinon, comment feriez-vous pour faire cela?
  2. Afin de m'assurer que les choses ne deviennent pas incontrôlables, dois-je implémenter un ThreadManager ou quelque chose de ce genre, qui démarre et s'arrête threads, et les redistribue quand ils sont complets? Par exemple, si j'ai 256 objets à analyser et 16 threads au total, le ThreadManager attribue le premier thread fini au 17ème objet à analyser, etc.
  3. Y a-t-il une différence dramatique entre Runnable/Callable autre que le fait que Callable peut renvoyer un résultat? Sinon devrais-je essayer d'implémenter ma propre interface, dans ce cas pourquoi?

Merci,

Author: AJcodez, 2011-02-09

4 answers

  1. Vous pouvez utiliser une implémentation BlockingQueue pour contenir vos objets et générer vos threads à partir de là. Cette interface est basée sur le principe producteur-consommateur . La méthodeput () bloquera si votre file d'attente est pleine jusqu'à ce qu'il y ait plus d'espace et la méthodetake () bloquera si la file d'attente est vide jusqu'à ce qu'il y ait à nouveau des objets dans la file d'attente.

  2. Un ExecutorService peut vous aider à gérer votre piscine de threads.

  3. Si vous attendez un résultat de vos threads générés, l'interface Callable est une bonne idée à utiliser car vous pouvez démarrer le calcul plus tôt et travailler dans votre code en supposant les résultats dans Future -s. En ce qui concerne les différences avec l'interface Runnable , à partir du ]}

    L'interface appelable est similaire à Runnable, en ce sens que les deux sont conçus pour les classes dont les instances sont potentiellement exécuté par un autre thread. Un Runnable, cependant, ne renvoie pas de résultat et ne peut pas lancer une exception vérifiée.

Quelques choses générales que vous devez considérer dans votre quête de la concurrence java:

  • La visibilité ne vient pas par défaut. volatile, AtomicReference et d'autres objets dans le java.util.simultané.atomic paquet sont vos amis.
  • Vous devez soigneusement assurer l'atomicité du composé actions utilisant la synchronisation et les verrous.
 3
Author: dimitrisli, 2011-02-09 10:59:03

Votre idée est fondamentalement saine. Cependant, plutôt que de créer des threads directement, ou indirectement via une sorte de ThreadManager de votre propre conception, utilisez un exécuteur à partir du package de concurrence Java. Il fait tout ce dont vous avez besoin, et d'autres personnes ont déjà pris le temps de l'écrire et de le déboguer. Un exécuteur gère une file d'attente de tâches, vous n'avez donc pas à vous soucier de fournir vous-même la file d'attente threadsafe.

Il n'y a pas de différence entre Callable et Runnable sauf que le premier renvoie une valeur. Les exécuteurs géreront les deux et les prépareront de la même manière.

Il n'est pas clair pour moi si vous prévoyez de faire de l'étape de préparation une tâche distincte des analyses, ou de la plier dans l'une d'entre elles, cette tâche engendrant l'autre tâche d'analyse à mi-chemin. Je ne peux penser à aucune raison de préférer fortement l'un à l'autre, mais c'est un choix auquel vous devriez penser.

 3
Author: Tom Anderson, 2011-02-09 10:42:44

Les exécuteurs fournissent des méthodes d'usine pour créer des pools de threads. Plus précisément Executors#newFixedThreadPool (int nThreads) crée un pool de threads avec une taille fixe qui utilise une file d'attente illimitée. De plus, si un thread se termine en raison d'une défaillance, un nouveau thread sera remplacé à sa place. Donc, dans votre exemple spécifique de 256 tâches et 16 threads, vous appelleriez

 // create pool
ExecutorService threadPool = Executors.newFixedThreadPool(16);
// submit task.
Runnable task = new Runnable(){};;
threadPool.submit(task);

La question importante est de déterminer le nombre approprié de threads pour votre pool de threads. Voyez si cela aide Nombre efficace de Threads

 2
Author: richs, 2017-05-23 12:29:01

Semble raisonnable, mais ce n'est pas aussi trivial à implémenter que cela puisse paraître. Peut-être que vous devriez vérifier le projet jsr166y. C'est probablement la solution la plus simple à votre problème.

 0
Author: proactif, 2011-02-09 10:34:37