Forcer plusieurs threads à utiliser plusieurs CPU lorsqu'ils sont disponibles

j'écris un programme Java qui utilise beaucoup de CPU en raison de la nature de ce qu'il fait. Cependant, beaucoup d'elle peuvent s'exécuter en parallèle, et j'ai fait mon programme multi-thread. Quand je l'exécute, il ne semble utiliser qu'un CPU jusqu'à ce qu'il en ait besoin de plus, il utilise un autre CPU - y a-t-il quelque chose que je puisse faire en Java pour forcer les différents threads à tourner sur différents noyaux/CPUs?

64
demandé sur Raedwald 2009-08-03 19:43:55

10 réponses

quand je l'exécute, il semble seulement utiliser un CPU jusqu'à ce qu'il ait besoin de plus utilise un autre CPU - y a-t-il quelque chose que je peut faire en Java pour forcer différent fils pour fonctionner sur différents carottes/Cpu?

j'interprète cette partie de votre question comme signifiant que vous avez déjà abordé le problème de rendre votre application multi-thread capable. Et malgré cela, il ne commence pas immédiatement à utiliser plusieurs cœurs.

La réponse à "est-il un moyen de forcer ..."N'est pas (AFAIK) directement. Votre JVM et / ou L'OS hôte décident du nombre de threads "natifs" à utiliser et de la façon dont ces threads sont connectés aux processeurs physiques. Vous avez quelques options pour le réglage. Par exemple, j'ai trouvé cette page qui parle de comment accorder Java threading sur Solaris. Et cette page parle d'autres choses qui peuvent ralentir une application multi-thread.

29
répondu Stephen C 2009-08-04 05:59:32

il y a deux méthodes de base pour multi-thread en Java. Chaque tâche logique que vous créez avec ces méthodes doit être exécutée sur un nouveau noyau lorsque nécessaire et disponible.

Méthode 1: définir un objet Runnable ou Thread (qui peut prendre un Runnable dans le constructeur) et le lancer en cours d'exécution avec le Thread.méthode start (). Il s'exécute sur n'importe quel noyau que L'OS lui donne -- généralement le moins chargé.

tutoriel: définition et démarrage des fils

Méthode 2: définissez les objets implémentant l'interface Runnable (s'ils ne renvoient pas de valeurs) ou Callable (s'ils le font), qui contiennent votre code de traitement. Passez ces tâches en tant que tâches à un Exécutorservice de java.util.simultanées paquet. Java.util.simultané.La classe Executors a un tas de méthodes pour créer des types d'Exécutorservices standard et utiles. Lien Les exécuteurs tutoriel.

d'après votre expérience personnelle, les pools de threads fixés et mis en cache sont très bons, bien que vous vouliez modifier le nombre de threads. Runtime.getRuntime ().les processeurs disponibles () peuvent être utilisés à l'exécution pour compter les cœurs disponibles. Vous aurez besoin de fermer les groupes de threads lorsque votre application est terminée, sinon l'application ne sortira pas parce que les threads ThreadPool restent en cours d'exécution.

obtenir une bonne performance multicore est parfois délicat, et plein de gotchas:

  • I/O Disque ralentit BEAUCOUP lorsqu'il est exécuté dans parallèle. Un seul fil doit faire lire/écrire le disque à la fois.
  • la synchronisation des objets assure la sécurité des opérations multi-filetées, mais ralentit le travail.
  • si les tâches sont trop trivial( petits morceaux de travail, exécuter rapide) les frais généraux de leur gestion dans un Exécutorservice coûte plus de vous gagnez de multiples core.
  • la création de nouveaux objets Thread est lente. Les ExecutorServices vont essayer de réutiliser les threads existants si possible.
  • toutes sortes de choses folles peuvent arriver quand plusieurs fils travaillent sur quelque chose. Gardez votre système simple et essayez de rendre les tâches logiquement distinctes et sans interaction.

un autre problème: contrôler le travail est difficile! Une bonne pratique est d'avoir un fil directeur qui crée et soumet tâches, puis quelques threads de travail avec des files d'attente de travail (en utilisant un ExecutorService).

je touche juste aux points clés ici -- la programmation multithread est considérée comme l'un des sujets de programmation les plus difficiles par de nombreux experts. C'est non intuitif, complexe, et les abstractions sont souvent faibles.


modifier -- exemple en utilisant ExecutorService:

public class TaskThreader {
    class DoStuff implements Callable {
       Object in;
       public Object call(){
         in = doStep1(in);
         in = doStep2(in);
         in = doStep3(in); 
         return in;
       }
       public DoStuff(Object input){
          in = input;
       }
    }

    public abstract Object doStep1(Object input);    
    public abstract Object doStep2(Object input);    
    public abstract Object doStep3(Object input);    

    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
        ArrayList<Callable> tasks = new ArrayList<Callable>();
        for(Object input : inputs){
           tasks.add(new DoStuff(input));
        }
        List<Future> results = exec.invokeAll(tasks);
        exec.shutdown();
        for(Future f : results) {
           write(f.get());
        }
    }
}
54
répondu BobMcGee 2009-08-04 00:07:52

tout d'abord, vous devez vous prouver que votre programme exécuterait plus rapide sur plusieurs noyaux. De nombreux systèmes d'exploitation s'efforcent d'exécuter des threads de programme sur le même noyau chaque fois que possible .

Fonctionnant sur le même noyau a de nombreux avantages. Le cache CPU est chaud, ce qui signifie que les données pour ce programme sont chargées dans le CPU. Les objets lock/monitor/synchronization sont en cache CPU ce qui signifie que les autres CPU pas besoin de faire des opérations de synchronisation de cache à travers le bus (coûteux!).

une chose qui peut très facilement faire fonctionner votre programme sur le même CPU tout le temps est la Sur-utilisation des serrures et de la mémoire partagée. Vos fils ne devraient pas se parler. Moins souvent vos threads utilisent les mêmes objets dans la même mémoire, plus souvent ils tournent sur des CPU différents. Le plus souvent ils utilisent la même mémoire, le plus souvent ils doivent bloquer en attendant l'autre fil.

chaque fois que L'OS voit un bloc de thread pour un autre thread, il exécutera ce thread sur le même CPU chaque fois qu'il le pourra. Il réduit la quantité de mémoire qui se déplace sur le bus inter-CPU. C'est ce que je suppose est la cause de ce que vous voyez dans votre programme.

17
répondu Zan Lynx 2010-09-14 19:04:28

tout d'abord, je suggérerais de lire "la concurrence dans la pratique" par Brian Goetz .

alt text

C'est de loin le meilleur livre décrivant la programmation java simultanée.

la Simultanéité est "facile à apprendre, difficile à maîtriser'. Je suggère de lire beaucoup sur le sujet avant d'essayer. Il est très facile d'obtenir un programme multi-threadé pour fonctionner correctement 99,9% du temps, et l'échec de 0,1%. Cependant, voici quelques conseils pour vous aider à démarrer:

il existe deux façons courantes de faire en sorte qu'un programme utilise plus d'un élément de base:

  1. faites tourner le programme en utilisant plusieurs processus. Un exemple est Apache compilé avec le MPM Pre-Fork, qui assigne les requêtes aux processus enfants. Dans un programme multi-processus, la mémoire n'est pas partagée par défaut. Cependant, vous pouvez mapper des sections de mémoire partagée à travers les processus. Apache fait ça avec 'tableau de bord'.
  2. faire le programme multi-threaded. Dans un programme multi-threadé, toute la mémoire tas est partagée par défaut. Chaque thread a encore sa propre pile, mais peut accéder à n'importe quelle partie du tas. Généralement, la plupart des programmes Java sont multi-threaded, et non multi-process.

au niveau le plus bas, on peut créer et détruire des fils . Grâce à Java, il est facile de créer des threads sur une plate-forme transversale portable.

comme il tend à devenir coûteux de créer et de détruire des threads tout le temps, Java inclut maintenant exécuteurs pour créer des pools de threads réutilisables. Les tâches peuvent être attribuées aux exécuteurs et le résultat peut être récupéré via un objet futur.

Généralement, on a une tâche qui peut être divisé en tâches plus petites, mais les résultats doivent être rassemblés. Par exemple, avec une sorte de fusion, on peut diviser la liste en des pièces de plus en plus petites, jusqu'à ce que chaque noyau fasse le tri. Cependant, comme chaque sous-liste est triée, il doit être fusionnées afin d'obtenir la dernière liste triée. Comme cette question de" diviser pour mieux régner "est assez courante, il existe un cadre JSR qui peut gérer la distribution sous-jacente et la jonction. Ce cadre sera probablement inclus dans Java 7.

8
répondu brianegge 2009-08-04 06:36:29

il n'y a aucun moyen de définir l'affinité CPU en Java. http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4234402

si vous devez le faire, utilisez JNI pour créer des threads natifs et définir leur affinité.

4
répondu Iouri Goussev 2009-12-06 23:16:20

vous devriez écrire votre programme pour faire son travail sous la forme d'un lot de Callable remis à un Exécutorservice et exécuté avec invokeAll(...).

vous pouvez alors choisir une implémentation appropriée à l'exécution de la classe Executors. Une suggestion serait d'appeler les Exécuteurs.newFixedThreadPool () avec un nombre correspondant à peu près au nombre de cœurs cpu pour rester occupé.

1
répondu Thorbjørn Ravn Andersen 2009-08-03 18:04:17

la chose la plus facile à faire est de décomposer votre programme en plusieurs processus. L'OS les répartira entre les noyaux.

un peu plus difficile est de casser votre programme en plusieurs threads et de faire confiance à la JVM pour les allouer correctement. C'est -- généralement -- ce que les gens font pour utiliser le matériel disponible.


modifier

comment un programme de multi-traitement peut-il être "plus facile"? Voici une étape dans un pipeline.

public class SomeStep {
    public static void main( String args[] ) {
        BufferedReader stdin= new BufferedReader( System.in );
        BufferedWriter stdout= new BufferedWriter( System.out );
        String line= stdin.readLine();
        while( line != null ) {
             // process line, writing to stdout
             line = stdin.readLine();
        }
    }
}

chaque étape du pipeline est structurée de la même façon. 9 lignes de frais généraux pour n'importe quel traitement est inclus.

ce n'est peut-être pas l'absolu le plus efficace. Mais c'est très facile.


la structure globale de vos processus concurrents n'est pas un problème JVM. C'est un problème de système D'exploitation, alors utilisez l'interpréteur de commandes.

java -cp pipline.jar FirstStep | java -cp pipline.jar SomeStep | java -cp pipline.jar LastStep

le seule chose qui reste est de travailler sur certains de sérialisation pour vos objets de données dans le pipeline. La sérialisation Standard fonctionne bien. Lire http://java.sun.com/developer/technicalArticles/Programming/serialization/ pour des conseils sur comment sérialiser. Pour ce faire, vous pouvez remplacer le BufferedReader et BufferedWriter par ObjectInputStream et ObjectOutputStream .

1
répondu S.Lott 2009-08-05 10:22:25

je pense que cette question est liée à Java Parallel Proccesing Framework (JPPF). En utilisant cela, vous pouvez exécuter différentes tâches sur différents processeurs.

1
répondu Nandika 2010-09-14 18:47:30

JVM performance tuning a déjà été mentionné dans pourquoi ce code Java n'utilise-t-il pas tous les cœurs CPU? . Notez que cela ne s'applique qu'à la JVM, de sorte que votre application doit déjà utiliser des threads (et plus ou moins "correctement"):

http://ch.sun.com/sunnews/events/2009/apr/adworkshop/pdf/5-1-Java-Performance.pdf

1
répondu ShiDoiSi 2017-05-23 11:47:29

vous pouvez utiliser ci-dessous L'API de Executors avec la version Java 8

public static ExecutorService newWorkStealingPool()

crée un pool de threads en utilisant tous les processeurs disponibles comme niveau de parallélisme cible.

en raison du mécanisme de vol de travail, threads inactifs voler les tâches de la file d'attente des threads occupés et le débit global va augmenter.

à Partir de grepcode , la mise en œuvre de newWorkStealingPool est la suivante

/**
     * Creates a work-stealing thread pool using all
     * {@link Runtime#availableProcessors available processors}
     * as its target parallelism level.
     * @return the newly created thread pool
     * @see #newWorkStealingPool(int)
     * @since 1.8
     */
    public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
1
répondu Ravindra babu 2016-05-02 18:11:28