¿Qué constituye el uso adecuado de hilos en la programación?

13

Estoy cansado de escuchar que la gente recomienda que use solo un subproceso por procesador, ¡mientras que muchos programas usan hasta 100 por proceso! tomar por ejemplo algunos programas comunes

vb.net ide uses about 25 thread when not debugging
System uses about 100
chrome uses about 19
Avira uses more than about 50

Cada vez que publico una pregunta relacionada con subprocesos, casi siempre me recuerdan que no debería usar más de un subproceso por procesador, y todos los programas que menciono anteriormente están arruinando mi sistema con un solo procesador.

Herrero
fuente
77
Esa recomendación es amplia. El límite de un subproceso por procesador es apropiado solo para aplicaciones vinculadas computacionalmente. La mayoría de los programas están vinculados a IO, ya sea tráfico de red, acceso a disco o incluso RAM. Es por eso que los servidores web, las bases de datos, etc. tienen grupos de subprocesos con muchos más subprocesos que los núcleos de procesador.
Kilian Foth
2
"¿Me recuerdan casi cada vez que no debería usar más de un hilo por procesador"? ¿Puedes publicar enlaces o ejemplos? ¿Casi siempre?
S.Lott
2
"... la gente recomienda que use solo un hilo por proceso". ¿Quienes son esas personas? La programación ha avanzado significativamente desde la Edad Media.
Rein Henrichs
2
No debe tener más de un subproceso de interfaz de usuario por proceso.
SLaks
3
@Billy ONeal, tu edición dejó sin sentido la pregunta
SK-logic

Respuestas:

22

solo debes usar un hilo por procesador,

Posiblemente en HPC, donde desea la máxima eficiencia, pero por lo demás, ¡la cosa más estúpida que he escuchado hoy!

Debe usar la cantidad de subprocesos que sean apropiados para el diseño del programa y que aún así ofrezcan un rendimiento aceptable.

Para un servidor web, podría ser razonable disparar un hilo para cada conexión entrante (aunque hay mejores formas para servidores muy cargados).

Para un ide cada herramienta que se ejecuta en su propio hilo no es irrazonable. Sospecho que muchos de los hilos informados para .Net IDE son cosas como el inicio de sesión y las tareas de E / S que se inician en sus propios hilos para que puedan continuar desbloqueados.

Martin Beckett
fuente
99
¡Ahora me tienes preguntando qué es la cosa más estúpida que has escuchado!
Michael K
3
@Michael: he enseñado a estudiantes universitarios y trabajado en contratos de defensa, ¡no creerías las cosas más estúpidas que he escuchado!
Martin Beckett
1
¿Los hemos visto en TheDailyWTF.com?
FrustratedWithFormsDesigner
Realmente no puedo encontrarlos ahora, pero mira este enlace social.msdn.microsoft.com/Forums/en-US/vbgeneral/thread/…
Smith
2
Tener como máximo un hilo enlazado a la CPU por procesador asignado a la aplicación. Los subprocesos vinculados a IO no son un gran problema (aparte de la memoria que consumen) y es importante recordar que las aplicaciones pueden restringirse para usar solo un subconjunto de las CPU del sistema; después de todo, es (generalmente) la computadora del usuario / administrador y no la del programador.
Donal Fellows
2

El consejo de un hilo por núcleo se aplica cuando el propósito es la velocidad a través de la ejecución paralela.

Una razón completamente diferente e igualmente válida es la simplicidad del código cuando tiene que responder a eventos impredecibles. Entonces, si un programa tiene que escuchar en 100 zócalos y parece prestar toda su atención a cada uno, es un uso perfecto para el enhebrado. Otro ejemplo es una interfaz de usuario, donde un hilo maneja los eventos de la interfaz de usuario, mientras que otro procesa en segundo plano.

Mike Dunlavey
fuente
1
El procesamiento vinculado a IO se puede realizar como un subproceso por origen de evento, o se pueden multiplexar múltiples orígenes de eventos en un solo subproceso. El código multiplexado suele ser más complejo y más eficiente.
Donal Fellows
2

Desea un subproceso para cada cálculo que puede proceder a velocidades diferentes que otros cálculos.

Para el cómputo paralelo de CPU, que viene en grandes bloques de trabajo, generalmente desea un subproceso por CPU, porque una vez que están ocupados, más subprocesos no ayudan y solo crean una sobrecarga del planificador. Si los bloques de trabajo tienen tamaños irregulares en el tiempo, o se generan dinámicamente en tiempo de ejecución (a menudo ocurre cuando tiene grandes estructuras de datos complejas para procesar), es posible que desee adjuntar esos bloques a muchos subprocesos, por lo que un planificador siempre tiene un gran configurado para elegir cuándo se completa algún bloque de trabajo, para mantener ocupadas todas las CPU.

Para el cálculo vinculado de E / S, generalmente desea un subproceso para cada "canal" de E / S independiente, ya que se comunican a diferentes velocidades, y los subprocesos bloqueados en el canal no impiden que otros subprocesos avancen.

Ira Baxter
fuente
Solo tenga en cuenta que este estilo de subprocesos puede conducir a algunos programas de arquitectura extraña. He visto un programa de 4 subprocesos que tenía un subproceso para leer registros de una tabla de base de datos, un subproceso para escribir registros transformados en un socket, un subproceso para leer las respuestas a esas escrituras de socket (que volvieron fuera de servicio y asincrónicamente), y un hilo para modificar el registro original de la base de datos con la respuesta. Se produjeron condiciones de error no intuitivas.
Bruce Ediger
Una opinión es que este estilo produce programas extraños. Otra opinión es que este es el estilo natural que los programas deberían haber tenido. No sé sobre las condiciones de error "no intuitivas"; si hay muchas cosas que suceden y una de ellas recibe un error, asegurarse de que se propague correctamente a través de los cálculos asincrónicos es un problema para muchos idiomas [estúpidamente, las excepciones de Java no están definidas en los límites del hilo], pero no lo es Un problema con el estilo del programa. (Nuestro lenguaje de programación PARLANSE [ver mi biografía] maneja las excepciones a través de los límites de los hilos de manera limpia, por lo que es posible hacerlo correctamente).
Ira Baxter
1

La regla general para los subprocesos es que desea al menos un subproceso de trabajo "activo" (capaz de ejecutar sus comandos inmediatamente dado el tiempo de CPU) para cada "unidad de ejecución" disponible en la computadora. Una "unidad de ejecución" es un procesador de instrucciones lógicas, por lo que un servidor hiperprocesado Xeon de cuatro núcleos y cuatro núcleos tendría 32 UE (4 chips, 4 núcleos por chip, cada uno hipertreprocesado). Su Core i7 promedio tendría 8.

Un subproceso por UE es el uso más completo de la potencia de la CPU, siempre que los subprocesos siempre estén en funcionamiento; este casi nunca es el caso, ya que los subprocesos necesitan acceso a la memoria no almacenada en caché, el disco duro, los puertos de red, etc. que deben esperar y que no requieren atención activa de la CPU para funcionar. Por lo tanto, puede aumentar aún más la eficiencia general con más hilos en cola y con muchas ganas de ir. Esto tiene un costo; cuando una CPU cambia un subproceso, debe almacenar en caché los registros del subproceso, el puntero de ejecución y otra información de estado que normalmente se mantiene en el funcionamiento más interno de una UE y se accede muy rápidamente, permitiendo que otras UE en ese chip de la CPU lo recojan. También requiere hilos en el sistema operativo para decidir a qué hilo se debe cambiar. Por último, cuando una UE cambia hilos, pierde las ganancias de rendimiento de la canalización que utilizan la mayoría de las arquitecturas de procesador; Tiene que lavar la tubería antes de cambiar los hilos. Pero, como todo esto todavía lleva mucho menos tiempo en promedio que simplemente esperar que el disco duro o incluso la RAM vuelvan con información, vale la pena el costo.

Sin embargo, en general, una vez que se supera el doble del número de subprocesos "activos" que los de la UE, el sistema operativo comienza a gastar más de los subprocesos de programación de tiempo de la UE, y las UE pasan más tiempo intercambiando entre ellos, de lo que se gastan en la ejecución de subprocesos activos de programas. Este es el punto de las deseconomías de escala; en realidad, llevará más tiempo ejecutar un algoritmo multiproceso si agregara un subproceso adicional en este punto.

Por lo tanto, en general, desea mantener al menos tantos subprocesos en su programa como UE en la computadora, pero desea evitar tener más del doble de ese número que no está esperando o durmiendo.

KeithS
fuente
Si N es el número de hilos y U el número de unidades, el OP cuestionó la regla "N = U". Lo estás relajando a una regla "U <= N <= 2 U". Iría un poco más allá y diría que "N <= c U" para una constante "razonablemente pequeña" (conocida por el programador) c es aceptable (si los puntos de referencia muestran un rendimiento razonable). Me preocuparía mucho si el número de hilos puede crecer hasta un número potencialmente ilimitado.
5gon12eder
1

Debe usar un hilo para:

Cada procesador que necesita para mantenerse ocupado.

Cada E / S puede ser útil al mismo tiempo que no se puede realizar sin bloqueo. (Por ejemplo, lee desde un disco local).

Cada tarea que requiere un subproceso dedicado, por ejemplo, llamar a una biblioteca que no tiene una interfaz sin bloqueo o donde las interfaces sin bloqueo no son apropiadas. Esto incluye tareas como monitorear el reloj del sistema, disparar temporizadores, etc.

Unos pocos adicionales para proteger contra bloqueos inesperados, como fallas de página.

Unos pocos más para proteger contra el bloqueo esperado que no vale la pena optimizar, por ejemplo, en código no crítico. (Por ejemplo, si rara vez necesita hacer una solicitud de DNS, probablemente no valga la pena hacer las solicitudes de DNS de forma asincrónica. Simplemente cree algunos hilos adicionales y le facilitará la vida).

Si sigue la regla de "un subproceso por procesador", entonces todo su código es crítico para el rendimiento. Cualquier código que se bloquee por alguna razón significa que su proceso no puede usar ese procesador. Eso hace que la programación sea mucho más difícil sin una buena razón.

David Schwartz
fuente
0

Puede generar procesos y subprocesos para permitir la utilización de un sistema multinúcleo \ multiprocesador para un solo programa, en cuyo caso no obtendrá ningún beneficio (al menos para el único programa) de tener más subprocesos \ procesos y núcleos.

O puede tener rutinas que sondeen un evento que normalmente bloquea la ejecución posterior. En lugar de atar la CPU con sondeo, en su lugar puede crear un hilo que se quedará en un estado inactivo hasta que el evento apropiado lo despierte. Este método se usa muy comúnmente en servidores web y colas de eventos de GUI. La mayoría de los programas quieren tener algún tipo de almacén de datos central (incluso si es el código de ejecución del programa) al que pueden acceder todos los subprocesos, así que supongo que es por eso que usan subprocesos en los procesos.

Peter Smith
fuente
0

Las aplicaciones que menciona rara vez ejecutan todas esas decenas de hilos simultáneamente. La mayoría de ellos simplemente se sientan allí porque están en un grupo de hilos . La aplicación envía varias tareas a una cola, que se purga mediante subprocesos en el grupo de subprocesos.

¿Por qué el tamaño de la piscina es tan grande entonces? Porque, a menudo, los subprocesos tienen que esperar otros recursos como el disco, la red, el usuario, algún otro subproceso, etc. Mientras un subproceso está esperando, es apropiado ejecutar otros subprocesos para utilizar completamente el procesador. Sin embargo, dimensionar la piscina de manera adecuada es complicado. Muy pocos subprocesos y perderá rendimiento porque el procesador no se utiliza por completo mientras espera algo. Demasiados subprocesos y perderá rendimiento debido al cambio entre ellos.

Joonas Pulakka
fuente