¿Cuántos hilos son demasiados?

312

Estoy escribiendo un servidor y envío cada acción a un hilo separado cuando se recibe la solicitud. Hago esto porque casi todas las solicitudes realizan una consulta a la base de datos. Estoy usando una biblioteca de subprocesos para reducir la construcción / destrucción de subprocesos.

Mi pregunta es: ¿cuál es un buen punto de corte para hilos de E / S como estos? Sé que sería una estimación aproximada, pero ¿estamos hablando de cientos? Miles?

¿Cómo podría averiguar cuál sería este límite?


EDITAR:

Gracias a todos por sus respuestas, parece que voy a tener que probarlo para averiguar mi límite de conteo de hilos. Sin embargo, la pregunta es: ¿cómo sé que he alcanzado ese techo? ¿Qué debo medir exactamente?

ryeguy
fuente
1
@ryeguy: El punto completo aquí es que no deberías establecer ningún máximo en el conjunto de subprocesos si no hay problemas de rendimiento para comenzar. La mayoría de los consejos de limitar un conjunto de subprocesos a ~ 100 subprocesos es ridículo, la mayoría de los conjuntos de subprocesos tienen / forma / más subprocesos que eso y nunca tienen un problema.
GEOCHET
ryeguy, mira además de mi respuesta a continuación sobre qué medir.
paxdiablo
No olvides que Python es por naturaleza, no realmente amigable con múltiples hilos. En cualquier momento, se está ejecutando un código de operación de un solo bytecode. Esto se debe a que Python emplea Global Interpreter Lock.
Pregunte
1
@ Jay D: Diría que el momento en que tocas el techo es cuando tu rendimiento comienza a caer.
ninjalj
66
@GEOCHET "El punto completo aquí es que no deberías establecer ningún máximo en el conjunto de hilos" Ummm ... ¿qué dices? Los grupos de subprocesos de tamaño fijo tienen los beneficios de una degradación y escalabilidad elegantes. Por ejemplo, en una configuración de red, si está generando nuevos subprocesos basados ​​en conexiones de clientes, sin un tamaño de grupo fijo corre el peligro muy real de aprender (por el camino difícil ) cuántos subprocesos puede manejar su servidor y cada cliente conectado sufrirá. Un grupo de tamaño fijo actúa como una válvula de tubería al impedir que su servidor intente morder más de lo que puede masticar.
b1nary.atr0phy

Respuestas:

206

Algunas personas dirían que dos hilos son demasiados, no estoy del todo en ese campo :-)

Aquí está mi consejo: medir, no adivinar. Una sugerencia es hacer que sea configurable e inicialmente configurarlo en 100, luego lanzar su software a la naturaleza y monitorear lo que sucede.

Si el uso de su hilo alcanza un máximo de 3, entonces 100 es demasiado. Si permanece en 100 durante la mayor parte del día, aumente hasta 200 y vea qué sucede.

En realidad, podría hacer que su propio código monitoree el uso y ajuste la configuración para la próxima vez que se inicie, pero eso probablemente sea excesivo.


Para aclaraciones y elaboración:

No estoy abogando por la creación de su propio subsistema de agrupación de subprocesos, utilice el que tiene. Pero, como estaba preguntando acerca de un buen punto de corte para los subprocesos, supongo que la implementación de su grupo de subprocesos tiene la capacidad de limitar el número máximo de subprocesos creados (lo cual es algo bueno).

He escrito código de agrupación de conexiones de bases de datos y subprocesos y tienen las siguientes características (que creo que son esenciales para el rendimiento):

  • Un número mínimo de hilos activos.
  • Un número máximo de hilos.
  • cerrar hilos que no se han usado durante un tiempo.

El primero establece una línea base para un rendimiento mínimo en términos del cliente del grupo de subprocesos (este número de subprocesos siempre está disponible para su uso). El segundo establece una restricción en el uso de recursos por hilos activos. El tercero lo regresa a la línea de base en tiempos de silencio para minimizar el uso de recursos.

Debe equilibrar el uso de recursos de tener subprocesos no utilizados (A) con el uso de recursos de no tener suficientes subprocesos para hacer el trabajo (B).

(A) es generalmente el uso de memoria (pilas, etc.) ya que un hilo que no funciona no utilizará gran parte de la CPU. (B) generalmente será un retraso en el procesamiento de las solicitudes a medida que lleguen, ya que debe esperar a que un hilo esté disponible.

Por eso lo mides. Como usted dice, la gran mayoría de sus hilos estarán esperando una respuesta de la base de datos para que no se ejecuten. Hay dos factores que afectan la cantidad de hilos que debe permitir.

El primero es el número de conexiones DB disponibles. Este puede ser un límite difícil a menos que pueda aumentarlo en el DBMS; voy a suponer que su DBMS puede tomar un número ilimitado de conexiones en este caso (aunque lo ideal es que también lo esté midiendo).

Luego, el número de hilos que debería tener depende de su uso histórico. El mínimo que debería tener en ejecución es el número mínimo que ha tenido en ejecución + A%, con un mínimo absoluto de (por ejemplo, y hacerlo configurable como A) 5.

El número máximo de hilos debe ser su máximo histórico + B%.

También debe estar monitoreando los cambios de comportamiento. Si, por alguna razón, su uso llega al 100% de lo disponible durante un tiempo significativo (de modo que afecte el rendimiento de los clientes), debe aumentar el máximo permitido hasta que nuevamente sea B% más alto.


En respuesta al "¿qué debo medir exactamente?" pregunta:

Lo que debe medir específicamente es la cantidad máxima de subprocesos en uso concurrente (por ejemplo, esperando un retorno de la llamada de DB) bajo carga. Luego agregue un factor de seguridad del 10%, por ejemplo (enfatizado, ya que otros carteles parecen tomar mis ejemplos como recomendaciones fijas).

Además, esto debe hacerse en el entorno de producción para el ajuste. Está bien obtener una estimación de antemano, pero nunca se sabe qué producción se le presentará (razón por la cual todas estas cosas deberían ser configurables en tiempo de ejecución). Esto es para detectar una situación como la duplicación inesperada de las llamadas entrantes del cliente.

paxdiablo
fuente
Si los hilos se generan en las solicitudes entrantes, el uso del hilo reflejará el número de solicitudes no atendidas. No hay forma de determinar el número "óptimo" a partir de esto. De hecho, encontrará más subprocesos que causan más contención de recursos y, por lo tanto, aumentará el número de subprocesos activos.
Andrew Grant
@ Andrew, la creación de subprocesos lleva tiempo, y puede determinar el número óptimo en función de los datos históricos [+ N%] (por lo tanto, medir, no adivinar). Además, más hilos solo causan contención de recursos cuando están trabajando, no esperando una señal / semáforo.
paxdiablo
¿Dónde están estos datos sobre la 'creación de subprocesos' que causan un problema de rendimiento al usar un grupo de subprocesos? Un buen grupo de subprocesos no estaría creando y destruyendo subprocesos entre tareas.
GEOCHET
@Pax Si todos sus hilos están esperando los mismos semáforos para ejecutar consultas DB, entonces esa es la definición misma de contención. Tampoco es cierto decir que los hilos no cuestan nada si están esperando un semáforo.
Andrew Grant
1
@ Andrew, no puedo ver por qué bloquearías semáforo las consultas de la base de datos, cualquier base de datos decente permitirá el acceso concurrente, con muchos hilos esperando las respuestas. Y los hilos no deberían costar ningún tiempo de ejecución mientras están bloqueados por semáforos, deberían permanecer en la cola bloqueada hasta que se libere el semáforo.
paxdiablo
36

Esta pregunta se ha discutido a fondo y no tuve la oportunidad de leer todas las respuestas. Pero aquí hay algunas cosas a tener en cuenta al mirar el límite superior en el número de hilos simultáneos que pueden coexistir pacíficamente en un sistema dado.

  1. Tamaño de la pila de subprocesos: en Linux, el tamaño predeterminado de la pila de subprocesos es de 8 MB (puede usar ulimit -a para averiguarlo).
  2. Memoria virtual máxima que admite una variante de sistema operativo determinada. Linux Kernel 2.4 admite un espacio de direcciones de memoria de 2 GB. con Kernel 2.6, yo un poco más grande (3GB)
  3. [1] muestra los cálculos para el número máximo de subprocesos por cada VM máxima admitida. Para 2.4 resulta ser aproximadamente 255 hilos. para 2.6 el número es un poco más grande.
  4. Qué tipo de planificador de kernel tienes. Comparando el planificador del kernel Linux 2.4 con 2.6, el último le ofrece una programación O (1) sin depender del número de tareas existentes en un sistema, mientras que el primero es más un O (n). Así también, las capacidades SMP de la programación del kernel también juegan un buen papel en el número máximo de subprocesos sostenibles en un sistema.

Ahora puede ajustar el tamaño de su pila para incorporar más subprocesos, pero luego debe tener en cuenta los gastos generales de la gestión de subprocesos (creación / destrucción y programación). Puede aplicar la afinidad de la CPU a un proceso determinado, así como a un subproceso determinado para vincularlos a CPU específicas para evitar gastos generales de migración de subprocesos entre las CPU y evitar problemas de liquidez.

Tenga en cuenta que uno puede crear miles de subprocesos a su gusto, pero cuando Linux se queda sin VM, comienza a matar procesos al azar (por lo tanto, subprocesos). Esto es para evitar que el perfil de la utilidad se maximice. (La función de utilidad informa sobre la utilidad de todo el sistema para una cantidad dada de recursos. Con recursos constantes en este caso CPU Cycles and Memory, la curva de utilidad se aplana con más y más cantidad de tareas).

Estoy seguro de que el programador de kernel de Windows también hace algo de este tipo para lidiar con la utilización excesiva de los recursos

[1] http://adywicaksono.wordpress.com/2007/07/10/i-can-not-create-more-than-255-threads-on-linux-what-is-the-solutions/

Jay D
fuente
17

Si sus hilos están realizando algún tipo de trabajo intensivo en recursos (CPU / Disco), rara vez verá beneficios más allá de uno o dos, y demasiados matarán el rendimiento muy rápidamente.

El "mejor de los casos" es que sus subprocesos posteriores se detendrán mientras se completan los primeros, o algunos tendrán bloqueos de bajo costo en recursos con poca contención. El peor de los casos es que comienzas a destruir la memoria caché / disco / red y tu rendimiento general cae por el suelo.

Una buena solución es colocar solicitudes en un grupo que luego se envían a los subprocesos de trabajo desde un grupo de subprocesos (y sí, evitar la creación / destrucción continua de subprocesos es un gran primer paso).

El número de subprocesos activos en este grupo se puede ajustar y escalar en función de los resultados de su creación de perfiles, el hardware en el que se está ejecutando y otras cosas que pueden estar ocurriendo en la máquina.

Andrew Grant
fuente
Sí, y debe usarse junto con una cola o grupo de solicitudes.
Andrew Grant
2
@ Andrew: ¿Por qué? Debe agregar una tarea al grupo de subprocesos cada vez que recibe una solicitud. Le corresponde al grupo de subprocesos asignar un subproceso para la tarea cuando hay uno disponible.
GEOCHET
Entonces, ¿qué haces cuando tienes cientos de solicitudes entrando y sin hilos? ¿Crear más? ¿Bloquear? ¿Devolver un error? Coloque sus solicitudes en un grupo que pueda ser tan grande como sea necesario y luego envíe estas solicitudes en cola a su grupo de subprocesos a medida que los subprocesos se liberen.
Andrew Grant
"se crean varios subprocesos para realizar una serie de tareas, que generalmente se organizan en una cola. Por lo general, hay muchas más tareas que subprocesos. Tan pronto como un subproceso complete su tarea, solicitará la siguiente tarea de la cola hasta que se hayan completado todas las tareas ".
GEOCHET
@ Andrew: No estoy seguro de qué grupo de subprocesos de Python está utilizando el OP, pero si desea un ejemplo del mundo real de esta funcionalidad que estoy describiendo: msdn.microsoft.com/en-us/library/…
GEOCHET
10

Una cosa que debe tener en cuenta es que python (al menos la versión basada en C) usa lo que se llama un bloqueo de intérprete global que puede tener un gran impacto en el rendimiento en máquinas de múltiples núcleos.

Si realmente necesita sacar el máximo provecho de Python multiproceso, puede considerar usar Jython o algo así.

Chad Okere
fuente
44
Después de leer esto, intenté ejecutar el tamiz de las tareas de Eratóstenes en tres hilos. Efectivamente, en realidad fue un 50% más lento que ejecutar las mismas tareas en un solo hilo. Gracias por el aviso. Estaba ejecutando Eclipse Pydev en una máquina virtual a la que se le asignaron dos CPU. A continuación, probaré un escenario que involucra algunas llamadas a la base de datos.
Don Kirkby
3
Hay dos (al menos) tipos de tareas: vinculadas a la CPU (por ejemplo, procesamiento de imágenes) y vinculadas a E / S (por ejemplo, descarga desde la red). Obviamente, el "problema" de GIL no afectará demasiado las tareas vinculadas de E / S. Si sus tareas están vinculadas a la CPU, entonces debería considerar el multiprocesamiento en lugar de multiprocesamiento.
iutinvg
1
Sí, el hilo de Python ha mejorado si tienes mucha red io. Lo cambio a hilo y obtuve 10 * más rápido que el código ordinario ...
tyan
8

Como Pax dijo correctamente, mida, no adivine . Eso fue lo que hice para DNSwitness y los resultados fueron sorprendentes: el número ideal de hilos era mucho más alto de lo que pensaba, algo así como 15,000 hilos para obtener los resultados más rápidos.

Por supuesto, depende de muchas cosas, por eso debes medirte a ti mismo.

¿Medidas completas (solo en francés) en Combien de fils d'exécution? .

bortzmeyer
fuente
1
15,000? Eso es un poco más alto de lo que hubiera esperado también. Aún así, si eso es lo que tienes, entonces eso es lo que tienes, no puedo discutir eso.
paxdiablo
2
Para esta aplicación específica, la mayoría de los subprocesos solo esperan una respuesta del servidor DNS. Entonces, cuanto más paralelismo, mejor, en tiempo de reloj de pared.
bortzmeyer
18
Creo que si tiene esos 15000 subprocesos que están bloqueando algunas E / S externas, entonces una mejor solución sería muchísimo menos subprocesos pero con un modelo asincrónico. Hablo por experiencia aquí.
Steve
5

He escrito varias aplicaciones muy multiproceso. En general, permito que un archivo de configuración especifique el número de subprocesos potenciales. Cuando ajusté para clientes específicos, configuré el número lo suficientemente alto como para que mi utilización de todos los núcleos de CPU fuera bastante alta, pero no tanto como para tener problemas de memoria (estos eran sistemas operativos de 32 bits en el hora).

Dicho de otra manera, una vez que llegue a un cuello de botella, ya sea CPU, rendimiento de la base de datos, rendimiento del disco, etc., agregar más hilos no aumentará el rendimiento general. Pero hasta que llegues a ese punto, ¡agrega más hilos!

Tenga en cuenta que esto supone que los sistemas en cuestión están dedicados a su aplicación y que no tiene que jugar bien (evite morir de hambre) a otras aplicaciones.

Matthew Lund
fuente
1
¿Puedes mencionar algunos de los números que has visto para contar hilos? Sería útil tener una idea de ello. Gracias.
kovac
3

La respuesta "gran hierro" es generalmente un subproceso por recurso limitado: procesador (enlazado a la CPU), armado (enlazado de E / S), etc., pero eso solo funciona si puede enrutar el trabajo al subproceso correcto para que el recurso ser accedido

Cuando eso no sea posible, tenga en cuenta que tiene recursos fungibles (CPU) y recursos no fungibles (brazos). Para las CPU no es crítico asignar cada subproceso a una CPU específica (aunque ayuda con la administración de la memoria caché), pero para los brazos, si no puede asignar un hilo al brazo, ingresa en la teoría de colas y cuál es el número óptimo para mantener los brazos ocupado. En general, estoy pensando que si no puede enrutar las solicitudes en función del brazo utilizado, tener 2-3 hilos por brazo será lo correcto.

Se produce una complicación cuando la unidad de trabajo pasada al hilo no ejecuta una unidad de trabajo razonablemente atómica. Por ejemplo, puede hacer que el hilo en un punto acceda al disco, en otro punto espere en una red. Esto aumenta el número de "grietas" en las que pueden entrar hilos adicionales y hacer un trabajo útil, pero también aumenta la oportunidad de que hilos adicionales contaminen las memorias caché de los demás, etc., y empantanen el sistema.

Por supuesto, debe sopesar todo esto contra el "peso" de un hilo. Desafortunadamente, la mayoría de los sistemas tienen subprocesos muy pesados ​​(y lo que llaman "subprocesos livianos" a menudo no son subprocesos), por lo que es mejor errar en el lado bajo.

Lo que he visto en la práctica es que las diferencias muy sutiles pueden marcar una enorme diferencia en cuántos hilos son óptimos. En particular, los problemas de caché y los conflictos de bloqueo pueden limitar en gran medida la cantidad de concurrencia práctica.

Hot Licks
fuente
2

Una cosa a considerar es cuántos núcleos existen en la máquina que ejecutará el código. Eso representa un límite estricto sobre cuántos subprocesos pueden continuar en un momento dado. Sin embargo, si, como en su caso, se espera que los subprocesos esperen con frecuencia que una base de datos ejecute una consulta, es probable que desee ajustar sus subprocesos en función de cuántas consultas simultáneas puede procesar la base de datos.

newdayrising
fuente
2
mmm no. El punto central de los subprocesos era (antes de que los procesadores multinúcleo y múltiples prevalecieran) es poder imitar tener múltiples procesadores en una máquina que tiene solo uno. Así es como se obtienen interfaces de usuario receptivas: un subproceso principal y subprocesos auxiliares.
mmr
1
@mmr: Um no. La idea de los subprocesos es permitir el bloqueo de E / S y otras tareas.
GEOCHET
44
La declaración que hice fue que el número de núcleos en una máquina representa un límite estricto en el número de hilos que pueden estar funcionando en un momento dado, lo cual es un hecho. Por supuesto, otros subprocesos pueden estar esperando que se completen las operaciones de E / S, y esta pregunta es una consideración importante.
newdayrising
1
De todos modos, tienes GIL en Python, lo que hace que los hilos solo sean teóricamente paralelos. No se puede ejecutar más de 1 subproceso simultáneamente, por lo que lo único importante es la capacidad de respuesta y las operaciones de bloqueo.
Abgan el
2
+1 Para comprender realmente cómo funcionan las computadoras. @mmr: Debe comprender la diferencia entre parecer tener múltiples procesadores y tener múltiples procesadores. @ Rich B: Un grupo de subprocesos es solo una de las muchas formas de manejar una colección de subprocesos. Es una buena, pero ciertamente no es la única.
llorar
2

Creo que esto es un poco esquivo a su pregunta, pero ¿por qué no dividirlos en procesos? Mi comprensión de las redes (desde los días nebulosos de antaño, en realidad no codifico redes en absoluto) fue que cada conexión entrante se puede manejar como un proceso separado, porque si alguien hace algo desagradable en su proceso, no lo hace. Destruye todo el programa.

mmr
fuente
1
Para Python eso es especialmente cierto, ya que múltiples procesos pueden ejecutarse en paralelo, mientras que múltiples hilos no lo hacen. Sin embargo, el costo es bastante alto. Debe iniciar un nuevo intérprete de Python cada vez y conectarse a DB con cada proceso (o usar algunas redirecciones de canalizaciones, pero también tiene un precio).
Abgan el
El cambio entre procesos es, la mayoría de las veces, más costoso que el cambio entre hilos (cambio de contexto completo en lugar de algunos registros). Al final, depende en gran medida de tu threading-lib. A medida que las preguntas giraban en torno al enhebrado, supongo que los procesos ya están fuera de discusión.
Leonidas
Lo suficientemente justo. Sin embargo, no estoy seguro de por qué es por eso que obtengo un ding -2 en el puntaje, a menos que la gente realmente quiera ver respuestas solo de hilo, en lugar de incluir otras respuestas que funcionen.
mmr
@mmr: Teniendo en cuenta que la pregunta era sobre / thread / pools, sí, creo que la gente debería esperar una respuesta sobre los hilos.
GEOCHET
La creación de procesos se puede realizar una vez al inicio (es decir, un grupo de procesos en lugar de un grupo de subprocesos). Amortizado durante la duración de la aplicación, esto puede ser pequeño. No pueden compartir información fácilmente, pero les compra la posibilidad de ejecutarse en múltiples CPU, por lo que esta respuesta es útil. +1.
paxdiablo
1

ryeguy, actualmente estoy desarrollando una aplicación similar y mi número de subprocesos está establecido en 15. Desafortunadamente, si lo aumento a 20, se bloquea. Entonces, sí, creo que la mejor manera de manejar esto es medir si su configuración actual permite o no más o menos que un número X de subprocesos.

hiperboreeano
fuente
55
Agregar a su conteo de hilos no debería bloquear aleatoriamente su aplicación. Hay alguna razon. Haría bien en averiguar la causa porque puede afectarlo incluso con menos hilos en algunas circunstancias, quién sabe.
Matthew Lund
-6

En la mayoría de los casos, debe permitir que el grupo de subprocesos maneje esto. Si publica algún código o proporciona más detalles, podría ser más fácil ver si hay alguna razón por la cual el comportamiento predeterminado del grupo de subprocesos no sería el mejor.

Puede encontrar más información sobre cómo debería funcionar aquí: http://en.wikipedia.org/wiki/Thread_pool_pattern

GEOCHET
fuente
1
@Pax: Esta no sería la primera vez que la mayoría de la gente no quería responder a la pregunta en cuestión (o entenderla). No estoy preocupado.
GEOCHET
-10

Tantos hilos como núcleos de CPU es lo que he escuchado muy a menudo.

Masfenix
fuente
55
@Rich, al menos explica por qué :-). Esta regla general solo se aplica cuando todos los hilos están vinculados a la CPU; obtienen una 'CPU' cada uno. Cuando muchos de los subprocesos están vinculados a E / S, generalmente es mejor tener muchos más subprocesos que 'CPU' (la CPU se cita ya que se aplica a subprocesos físicos de ejecución, por ejemplo, núcleos).
paxdiablo
1
@Abgan, no estaba seguro de eso, pensando que quizás Python crearía subprocesos "reales" del sistema operativo (se ejecutan en múltiples CPU). Si lo que usted dice es cierto (no tengo ninguna razón para dudar), entonces la cantidad de CPU no tiene relación: el enhebrado es útil solo cuando la mayoría de los subprocesos están esperando algo (por ejemplo, DB I / O).
paxdiablo
1
@Rich: cuando el subprocesamiento (real), el recuento de CPU TIENE un impacto, ya que puede ejecutar múltiples subprocesos sin espera de forma simultánea. Con una CPU, solo se ejecuta una y el beneficio se acumula al tener muchos otros subprocesos esperando un recurso que no sea de la CPU.
paxdiablo
1
@Pax: No entiendes el concepto de grupos de subprocesos, entonces supongo.
GEOCHET
1
@ Rich, entiendo bien los grupos de hilos; Parece que yo (y otros aquí) también entiendo el hardware mejor que tú. Con una CPU, solo se puede ejecutar un subproceso de ejecución, incluso si hay otros esperando una CPU. Dos CPU, dos pueden ejecutarse. Si y sólo si todos los hilos están esperando por una CPU, número de hilos ideal es igual a ...
paxdiablo