¿Por qué se dice que crear un hilo es caro?


Los tutoriales de Java dicen que crear un hilo es caro. Pero ¿por qué exactamente es caro? ¿Qué sucede exactamente cuando se crea un subproceso Java que hace que su creación sea costosa? Estoy tomando la declaración como verdadera, pero solo estoy interesado en la mecánica de la creación de hilos en JVM.

Sobrecarga del ciclo de vida del hilo. La creación de hilos y el desmontaje no son gratuitos. La sobrecarga real varía entre las plataformas, pero la creación de subprocesos lleva tiempo, lo que introduce latencia en la solicitud procesamiento, y requiere alguna actividad de procesamiento por parte de la JVM y el sistema operativo. Si las solicitudes son frecuentes y ligeras, como en la mayoría de las aplicaciones de servidor, crear un nuevo subproceso para cada solicitud puede consumir recursos informáticos significativos.

Desde La concurrencia de Java en la práctica
Por Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea
Imprimir ISBN-10: 0-321-34960-1

Author: Peter Lawrey, 2011-03-30

6 answers

La creación de subprocesos Java es costosa porque hay un poco de trabajo involucrado:

  • Un gran bloque de memoria tiene que ser asignado e inicializado para la pila de subprocesos.
  • Se deben realizar llamadas al sistema para crear / registrar el subproceso nativo con el sistema operativo host.
  • Los descriptores deben ser creados, inicializados y añadidos a las estructuras de datos internas de JVM.

También es caro en el sentido de que el hilo ata los recursos mientras esté vivo; por ejemplo, la pila de subprocesos, cualquier objeto accesible desde la pila, los descriptores de subprocesos JVM, los descriptores de subprocesos nativos del sistema operativo.

Los costos de todas estas cosas son específicos de la plataforma, pero no son baratos en ninguna plataforma Java que haya encontrado.


Una búsqueda en Google me encontró un antiguo punto de referencia que informa una tasa de creación de subprocesos de ~4000 por segundo en un Sun Java 1.4.1 en un 2002 vintage dual processor Xeon ejecutando 2002 vintage Linux. Una plataforma más moderna dará mejores números ... y no puedo comentar la metodología ... pero al menos da un estadio para lo caro que es probable que sea la creación de hilos.

La evaluación comparativa de Peter Lawrey indica que la creación de subprocesos es significativamente más rápida en estos días en términos absolutos, pero no está claro cuánto de esto se debe a mejoras en Java y/o el sistema operativo ... o velocidades de procesador más rápidas. Pero sus números aún indican una mejora de más de 150 veces si usas un grupo de subprocesos versus crear / iniciar un hilo nuevo cada vez. (Y señala que todo esto es relativo ...)


(Lo anterior asume "hilos nativos" en lugar de "hilos verdes", pero los JVM modernos usan hilos nativos por razones de rendimiento. Los hilos verdes son posiblemente más baratos de crear, pero usted paga por ello en otras áreas.)


He hecho un poco de investigación para ver cómo la pila de un hilo de Java realmente se asigna. En el caso de OpenJDK 6 en Linux, la pila de subprocesos es asignado por la llamada a pthread_create que crea el hilo nativo. (La JVM no pasa pthread_create una pila preasignada.)

Luego, dentro de pthread_create la pila se asigna mediante una llamada a mmap de la siguiente manera:

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

De acuerdo con man mmap, la bandera MAP_ANONYMOUS hace que la memoria se inicialice a cero.

Por lo tanto, a pesar de que podría no ser esencial que las nuevas pilas de subprocesos de Java se pongan a cero (según la especificación JVM), en la práctica (al menos con OpenJDK 6 en Linux) se ponen a cero.

 134
Author: Stephen C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-18 11:35:21

Otros han discutido de dónde provienen los costos del roscado. Esta respuesta cubre por qué crear un subproceso no es tan caro en comparación con muchas operaciones, sino relativamente caro en comparación con las alternativas de ejecución de tareas, que son relativamente menos costosas.

La alternativa más obvia a ejecutar una tarea en otro subproceso es ejecutar la tarea en el mismo subproceso. Esto es difícil de entender para aquellos que asumen que más hilos siempre son mejores. La lógica es que si la sobrecarga de agregar la tarea a otro subproceso es mayor que el tiempo que ahorra, puede ser más rápido realizar la tarea en el subproceso actual.

Otra alternativa es usar un grupo de subprocesos. Un grupo de hilos puede ser más eficiente por dos razones. 1) reutiliza los hilos ya creados. 2) puede ajustar/controlar el número de hilos para garantizar un rendimiento óptimo.

El siguiente programa 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

Esta es una prueba para una tarea trivial que expone la sobrecarga de cada opción de roscado. (Esta tarea de prueba es el tipo de tarea que en realidad se realiza mejor en el subproceso actual.)

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);
    }
}
}

Como puedes ver, crear un nuevo hilo solo cuesta ~70 µs. Esto podría considerarse trivial en muchos, si no en la mayoría de los casos de uso. Relativamente hablando, es más caro que las alternativas y para algunas situaciones, un grupo de subprocesos o no usar subprocesos en absoluto es una mejor solución.

 67
Author: Peter Lawrey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-28 07:33:41

En teoría, esto depende de la JVM. En la práctica, cada hilo tiene una cantidad relativamente grande de memoria de pila (256 KB por defecto, creo). Además, los subprocesos se implementan como subprocesos del sistema operativo, por lo que crearlos implica una llamada al sistema operativo, es decir, un cambio de contexto.

Darse cuenta de que "caro" en la computación es siempre muy relativo. La creación de subprocesos es muy costosa en relación con la creación de la mayoría de los objetos, pero no muy costosa en relación con una búsqueda aleatoria de disco duro. No tienes que evitar crear hilos a toda costa, pero crear cientos de ellos por segundo no es un movimiento inteligente. En la mayoría de los casos, si su diseño requiere muchos subprocesos, debe usar un grupo de subprocesos de tamaño limitado.

 28
Author: Michael Borgwardt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-10-14 09:16:30

Hay dos tipos de hilos:

  1. Hilos apropiados: estas son abstracciones alrededor de las instalaciones de subprocesos del sistema operativo subyacente. La creación de hilos es, por lo tanto, tan costosa como la del sistema always siempre hay una sobrecarga.

  2. "Green" threads: creados y programados por la JVM, estos son más baratos, pero no se produce ningún paralelismo adecuado. Estos se comportan como subprocesos, pero se ejecutan dentro del subproceso JVM en el sistema operativo. No son a menudo usado, que yo sepa.

El factor más grande que puedo pensar en la sobrecarga de creación de subprocesos, es el tamaño de pila que ha definido para sus subprocesos. El tamaño de la pila de subprocesos se puede pasar como parámetro cuando se ejecuta la VM.

Aparte de eso, la creación de subprocesos depende principalmente del sistema operativo e incluso de la implementación de VM.

Ahora, permítanme señalar algo: crear hilos es caro si usted está planeando en disparar 2000 hilos por segundo, cada segundo de tu tiempo de ejecución. La JVM no está diseñada para manejar eso. Si tienes un par de trabajadores de establos que no serán despedidos y asesinados una y otra vez, relájate.

 8
Author: slezica,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-30 07:17:04

Crear Threads requiere asignar una buena cantidad de memoria, ya que no tiene que hacer una, sino dos pilas nuevas (una para el código java, otra para el código nativo). El uso de Ejecutores/Grupos de subprocesos puede evitar la sobrecarga, reutilizando subprocesos para múltiples tareas para Ejecutor.

 6
Author: Philip JF,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-13 10:17:35

Obviamente el quid de la cuestión es qué significa "caro".

Un subproceso necesita crear una pila e inicializar la pila basándose en el método run.

Necesita configurar estructuras de estado de control, es decir, en qué estado se puede ejecutar, en espera, etc.

Probablemente hay una buena cantidad de sincronización alrededor de la configuración de estas cosas.

 0
Author: MeBigFatGuy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-30 07:18:33