web-dev-qa-db-fra.com

Pourquoi la création d'un fil est-elle coûteuse?

Les tutoriels Java indiquent que la création d'un fil est coûteuse. Mais pourquoi est-ce si cher? Qu'est-ce qui se passe exactement lorsqu'un Java est créé) pour en permettre la création? Je prends la déclaration comme vraie, mais je suis simplement intéressé par la mécanique de la création de threads dans la JVM.

Cycle de vie du fil supérieur. La création et le démontage de threads ne sont pas gratuits. La surcharge réelle varie d'une plate-forme à l'autre, mais la création de threads prend du temps, introduisant une latence dans le traitement des demandes et nécessite une activité de traitement de la part de la JVM et du système d'exploitation. Si les demandes sont fréquentes et légères, comme dans la plupart des applications serveur, la création d'un nouveau thread pour chaque demande peut consommer des ressources informatiques importantes.

De concurrence de Java en pratique
Par Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes et Doug Lea
Imprimer ISBN-10: 0-321-34960-1

167
kachanov

La création de threads Java est coûteuse car elle demande beaucoup de travail:

  • Un grand bloc de mémoire doit être alloué et initialisé pour la pile de threads.
  • Des appels système doivent être effectués pour créer/enregistrer le thread natif avec le système d'exploitation hôte.
  • Les descripteurs doivent être créés, initialisés et ajoutés aux structures de données internes de la machine virtuelle Java.

Il est également coûteux en ce sens que le fil relie des ressources tant qu’il est en vie; par exemple. la pile de threads, tous les objets accessibles à partir de la pile, les descripteurs de threads JVM, les descripteurs de threads natifs du système d'exploitation.

Les coûts de toutes ces choses sont spécifiques à la plate-forme, mais ils ne sont pas bon marché sur toutes les Java que j'ai rencontrées).


Une recherche sur Google m'a trouvé un ancien critère qui indique un taux de création de threads d'environ 4 000 par seconde sur un Soleil Java 1.4.1 sur un Xeon à double processeur datant de 2002) Linux de 2002: une plate-forme plus moderne donnera de meilleurs chiffres ... et je ne saurais en dire plus sur la méthodologie ... mais au moins, cela donne une idée approximative du coût création de thread est susceptible d'être.

L'analyse comparative de Peter Lawrey indique que la création de threads est nettement plus rapide de nos jours en termes absolus, mais on ignore à quel point cela est dû aux améliorations apportées à Java et/ou au système d'exploitation ... ou à des vitesses de traitement plus rapides Mais ses chiffres toujours indiquent une amélioration de plus de 150 fois si vous utilisez un pool de threads par rapport à la création/démarrage d'un nouveau thread à chaque fois. soulignez que tout cela est relatif ...)


(Ce qui précède suppose des "threads natifs" plutôt que des "threads verts", mais les machines virtuelles modernes utilisent toutes des threads natifs pour des raisons de performances. Les threads verts sont peut-être moins coûteux à créer, mais vous payez pour d'autres domaines.)


J'ai creusé un peu pour voir comment la pile d'un thread Java est réellement allouée. Dans le cas d'OpenJDK 6 sous Linux, la pile de thread est allouée par l'appel de pthread_create qui crée le thread natif. (La machine virtuelle Java ne passe pas pthread_create une pile préallouée.)

Ensuite, dans les pthread_create la pile est allouée par un appel à mmap comme suit:

mmap(0, attr.__stacksize, 
     PROT_READ|PROT_WRITE|PROT_EXEC, 
     MAP_PRIVATE|MAP_ANONYMOUS, -1, 0)

Selon man mmap, le MAP_ANONYMOUS drapeau permet d'initialiser la mémoire à zéro.

Ainsi, même s’il n’est peut-être pas essentiel que les nouvelles Java soient mises à zéro (conformément à la spécification de la machine virtuelle Java)), en pratique (du moins avec OpenJDK 6 sous Linux), elles le sont également.

142
Stephen C

D'autres ont discuté de l'origine des coûts du filetage. Cette réponse explique pourquoi la création d'un thread n'est pas si coûteuse comparée à de nombreuses opérations, mais relativement coûteuse comparée aux alternatives d'exécution de tâches, qui sont relativement moins coûteuse.

L'alternative la plus évidente à l'exécution d'une tâche dans un autre thread consiste à l'exécuter dans le même thread. C'est difficile à comprendre pour ceux qui supposent que plus de threads sont toujours meilleurs. La logique est que si l'ajout de la tâche à un autre thread nécessite plus de temps que le temps que vous enregistrez, il peut être plus rapide d'exécuter la tâche dans le thread actuel.

Une autre alternative consiste à utiliser un pool de threads. Un pool de threads peut être plus efficace pour deux raisons. 1) il réutilise les threads déjà créés. 2) vous pouvez régler/contrôler le nombre de threads afin d’obtenir des performances optimales.

Le programme suivant imprime ....

Time for a task to complete in a new Thread 71.3 us
Time for a task to complete in a thread pool 0.39 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 65.4 us
Time for a task to complete in a thread pool 0.37 us
Time for a task to complete in the same thread 0.08 us
Time for a task to complete in a new Thread 61.4 us
Time for a task to complete in a thread pool 0.38 us
Time for a task to complete in the same thread 0.08 us

Ceci est un test pour une tâche triviale qui expose le surcoût de chaque option de thread. (Cette tâche de test est le type de tâche qu'il est préférable de réaliser dans le thread actuel.)

final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
Runnable task = new Runnable() {
    @Override
    public void run() {
        queue.add(1);
    }
};

for (int t = 0; t < 3; t++) {
    {
        long start = System.nanoTime();
        int runs = 20000;
        for (int i = 0; i < runs; i++)
            new Thread(task).start();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a new Thread %.1f us%n", time / runs / 1000.0);
    }
    {
        int threads = Runtime.getRuntime().availableProcessors();
        ExecutorService es = Executors.newFixedThreadPool(threads);
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            es.execute(task);
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in a thread pool %.2f us%n", time / runs / 1000.0);
        es.shutdown();
    }
    {
        long start = System.nanoTime();
        int runs = 200000;
        for (int i = 0; i < runs; i++)
            task.run();
        for (int i = 0; i < runs; i++)
            queue.take();
        long time = System.nanoTime() - start;
        System.out.printf("Time for a task to complete in the same thread %.2f us%n", time / runs / 1000.0);
    }
}
}

Comme vous pouvez le constater, la création d'un nouveau thread ne coûte qu'environ 70 µs. Cela pourrait être considéré comme trivial dans beaucoup, sinon la plupart des cas d'utilisation. Relativement parlant, il est plus coûteux que les alternatives et dans certaines situations, un pool de threads ou ne pas utiliser de threads est une meilleure solution.

71
Peter Lawrey

En théorie, cela dépend de la machine virtuelle Java. En pratique, chaque thread a une quantité de mémoire de pile relativement importante (256 Ko par défaut, je pense). De plus, les threads étant implémentés en tant que threads de système d’exploitation, leur création implique un appel de système d’exploitation, c’est-à-dire un commutateur de contexte.

Réalisez que "cher" en informatique est toujours très relatif. La création de threads est très chère par rapport à la création de la plupart des objets, mais pas très chère par rapport à une recherche aléatoire de disque dur. Vous n'avez pas à éviter de créer des threads à tout prix, mais en créer des centaines par seconde n'est pas une décision judicieuse. Dans la plupart des cas, si votre conception nécessite beaucoup de threads, vous devez utiliser un pool de threads de taille limitée.

29
Michael Borgwardt

Il existe deux types de threads:

  1. threads appropriés: ce sont des abstractions autour des fonctionnalités de threading du système d'exploitation sous-jacent. La création de threads est donc aussi coûteuse que celle du système - il y a toujours une surcharge.

  2. "Green" threads: créés et planifiés par la JVM, ils sont moins chers, mais aucun parallélisme approprié ne se produit. Ils se comportent comme des threads, mais sont exécutés dans le thread JVM du système d'exploitation. Ils ne sont pas souvent utilisés, à ma connaissance.

Le facteur le plus important auquel je puisse penser dans la surcharge de création de threads est le taille de pile que vous avez défini pour vos threads. La taille de pile de threads peut être passée en tant que paramètre lors de l'exécution de la machine virtuelle.

En dehors de cela, la création de threads dépend principalement du système d'exploitation, voire de l'implémentation de VM.

Maintenant, permettez-moi de souligner quelque chose: la création de threads coûte cher si vous envisagez de tirer 2000 threads par seconde, chaque seconde de votre exécution. La JVM n'est pas conçue pour gérer cela. Si vous avez quelques travailleurs stables qui ne seront pas licenciés et tués encore et encore, détendez-vous.

8
slezica

Créer Threads nécessite d'allouer une quantité importante de mémoire car il doit créer non pas une, mais deux nouvelles piles (une pour Java, une pour le code natif). Utilisation de - Executors /Les pools de threads peuvent éviter la surcharge en réutilisant les threads pour plusieurs tâches pour Executor .

6
Philip JF

De toute évidence, le noeud de la question est de savoir ce que signifie "cher".

Un thread doit créer une pile et l'initialiser en fonction de la méthode d'exécution.

Il doit mettre en place des structures d’état de contrôle, c’est-à-dire quel état il est en cours d’exécution, en attente, etc.

Il y a probablement beaucoup de synchronisation autour de la mise en place de ces choses.

0
MeBigFatGuy