¿Cuándo necesitarías "cientos de miles" de hilos?

31

Erlang, Go y Rust afirman de una manera u otra que admiten la programación concurrente con "hilos" / corutinas baratos. Las preguntas frecuentes de Go indican:

Es práctico crear cientos de miles de gorutinas en el mismo espacio de direcciones.

El tutorial de óxido dice:

Debido a que las tareas son significativamente más baratas de crear que los hilos tradicionales, Rust puede crear cientos de miles de tareas concurrentes en un sistema típico de 32 bits.

La documentación de Erlang dice:

El tamaño de almacenamiento dinámico inicial predeterminado de 233 palabras es bastante conservador para admitir sistemas Erlang con cientos de miles o incluso millones de procesos.

Mi pregunta: ¿qué tipo de aplicación requiere tantos hilos concurrentes de ejecución? Solo los servidores web más ocupados reciben incluso miles de visitantes simultáneos. Las aplicaciones de tipo jefe-trabajador / despachador de trabajo que he escrito alcanzan rendimientos decrecientes cuando el número de subprocesos / procesos es mucho mayor que el número de núcleos físicos. Supongo que podría tener sentido para aplicaciones numéricas, pero en realidad la mayoría de la gente delega paralelismo a bibliotecas de terceros escritas en Fortran / C / C ++, no en estos lenguajes de nueva generación.

usuario39019
fuente
55
Creo que la fuente de su confusión es esta: estos microthreads / tareas / etc. no están destinados principalmente como un sustituto de los hilos / procesos del sistema operativo del que habla, ni están destinados a dividir una gran cantidad de números fácilmente paralelizables. entre unos pocos núcleos (como usted comentó correctamente, no tiene sentido tener 100k hilos en 4 núcleos para ese propósito).
us2012
1
Entonces, ¿para qué están destinados? Tal vez soy un ingenuo, pero nunca me he encontrado con una situación en la que la introducción de corutinas / etc hubiera simplificado un programa de ejecución de un solo hilo. Y he podido lograr niveles "bajos" de concurrencia con los procesos, que en Linux puedo lanzar cientos o miles sin sudar.
user39019
Tendría poco sentido tener tantas tareas realmente funcionando. Eso no significa que no pueda tener una gran cantidad de tareas que en su mayoría simplemente se bloquearon esperando que sucediera algo.
Loren Pechtel
55
La idea de la asincronía basada en tareas frente a la asincronía basada en subprocesos es decir que el código de usuario debe concentrarse en las tareas que deben realizarse en lugar de administrar a los trabajadores que realizan esas tareas. Piense en un hilo como un trabajador que contrata; contratar a un trabajador es costoso, y si lo hace, quiere que trabaje duro en la mayor cantidad de tareas posible el 100% del tiempo. Muchos sistemas pueden caracterizarse por tener cientos o miles de tareas pendientes, pero no necesita cientos o miles de trabajadores.
Eric Lippert
Continuando con el comentario de @ EricLippert, hay varias situaciones en las que existirían cientos de miles de tareas. Ejemplo # 1: la descomposición de una tarea de datos paralelos, como el procesamiento de imágenes. Ejemplo # 2: un servidor que admite cientos de miles de clientes, cada uno de los cuales podría emitir un comando en cualquier momento. Cada tarea habría requerido su propio "contexto de ejecución ligero": la capacidad de recordar en qué estado se encuentra (protocolos de comunicación) y el comando que está ejecutando actualmente, y poco más. El peso ligero es posible siempre que cada uno tenga una pila de llamadas poco profunda.
rwong

Respuestas:

19

un caso de uso: websockets:
como los websockets son de larga duración en comparación con las solicitudes simples, en un servidor ocupado se acumularán muchos websockets con el tiempo. microthreads le brinda un buen modelado conceptual y también una implementación relativamente fácil.

más en general, los casos en que numerosas unidades más o menos autónomas están esperando que ocurran ciertos eventos deberían ser buenos casos de uso.

kr1
fuente
15

Podría ayudar pensar en lo que Erlang fue diseñado originalmente para hacer, que era administrar las telecomunicaciones. Actividades como enrutamiento, conmutación, recopilación / agregación de sensores, etc.

Llevar esto al mundo web: considere un sistema como Twitter . El sistema probablemente no usaría microthreads en la generación de páginas web, pero podría usarlos en su colección / almacenamiento en caché / distribución de tweets.

Este artículo podría ser de más ayuda.

Dave Clausen
fuente
11

En un lenguaje en el que no está permitido modificar variables, el simple acto de mantener el estado requiere un contexto de ejecución separado (que la mayoría de las personas llamaría un hilo y Erlang llama un proceso). Básicamente, todo es un trabajador.

Considere esta función de Erlang, que mantiene un contador:

counter(Value) ->
    receive                               % Sit idle until a message is received
        increment -> counter(Value + 1);  % Restart with incremented value
        decrement -> counter(Value - 1);  % Restart with decremented value
        speak     ->
            io:fwrite("~B~n", [Value]),
            counter(Value);               % Restart with unaltered value
        _         -> counter(Value)       % Anything else?  Do nothing.
    end.

En un lenguaje OO convencional como C ++ o Java, lo lograrías teniendo una clase con un miembro de clase privada, métodos públicos para obtener o cambiar su estado y un objeto instanciado para cada contador. Erlang reemplaza la noción del objeto instanciado con un proceso, la noción de métodos con mensajes y el mantenimiento del estado con llamadas de cola que reinician la función con cualquier valor que forme el nuevo estado. El beneficio oculto en este modelo, y la mayor parte de la razón de ser de Erlang, es que el lenguaje serializa automáticamente el acceso al valor del contador mediante el uso de una cola de mensajes, lo que hace que el código concurrente sea muy fácil de implementar con un alto grado de seguridad .

Probablemente esté acostumbrado a la idea de que los cambios de contexto son caros, lo que sigue siendo cierto desde la perspectiva del sistema operativo host. El tiempo de ejecución de Erlang es en sí mismo un pequeño sistema operativo sintonizado, por lo que cambiar entre sus propios procesos es rápido y eficiente, todo mientras mantiene el número de cambios de contexto que el sistema operativo hace al mínimo. Por esta razón, tener miles de procesos no es un problema y se recomienda.

Blrfl
fuente
1
Su última aplicación de counter/1debería usar una c minúscula;) Traté de arreglarlo, pero a StackExchange no le gustan las ediciones de 1 carácter.
d11wtq
4

Mi pregunta: ¿qué tipo de aplicación requiere tantos hilos concurrentes de ejecución?

1) El hecho de que un idioma "escale" significa que hay menos posibilidades de que abandones ese idioma cuando las cosas se vuelvan más complejas en el futuro. (Esto se llama el concepto de "Producto completo"). Muchas personas están abandonando Apache para Nginx por esta misma razón. Si estás cerca del "límite duro" impuesto por la sobrecarga del hilo, te asustarás y comenzarás a pensar en formas de superarlo. Los sitios web nunca pueden predecir cuánto tráfico obtendrán, por lo que pasar un poco de tiempo haciendo que las cosas sean escalables es razonable.

2) Una gorutina por solicitud, solo el comienzo. Hay muchas razones para usar gorutinas internamente.

  • Considere una aplicación web con solicitudes simultáneas de 100, pero cada solicitud genera cientos de solicitudes de fondo. El ejemplo obvio es un agregador de motor de búsqueda. Pero casi cualquier aplicación podría crear gorutinas para cada "área" en pantalla, y luego generarlas de forma independiente en lugar de secuencialmente. Por ejemplo, cada página en Amazon.com está compuesta por más de 150 solicitudes de fondo, ensambladas solo para usted. No se da cuenta porque están en paralelo, no en secuencia, y cada "área" es su propio servicio web.
  • Considere cualquier aplicación donde la confiabilidad y la latencia sean primordiales. Probablemente desee que cada solicitud entrante active algunas solicitudes de fondo y devuelva los datos que se devuelvan primero .
  • Considere cualquier "cliente unirse" hecho en su aplicación. En lugar de decir "para cada elemento, obtener datos", puede escindir un montón de gorutinas. Si tiene que consultar un montón de DB esclavos, mágicamente irá N más rápido. Si no lo hace, no será más lento.

golpea rendimientos decrecientes cuando el número de subprocesos / procesos es mucho mayor que el número de núcleos físicos

El rendimiento no es la única razón para dividir un programa en CSP . En realidad, puede hacer que el programa sea más fácil de entender, y algunos problemas se pueden resolver con mucho menos código.

Como en las diapositivas vinculadas anteriormente, tener concurrencia en su código es una forma de organizar el problema. No tener gorutinas es como no tener una estructura de datos de Mapa / Dictonario / Hash en su idioma. Puedes sobrevivir sin eso. Pero una vez que lo tiene, comienza a usarlo en todas partes, y realmente simplifica su programa.

En el pasado, esto significaba "rodar su propia" programación multiproceso. Pero esto era complejo y peligroso: todavía no hay muchas herramientas para asegurarte de que no estás creando razas. ¿Y cómo evita que un futuro mantenedor cometa un error? Si observa programas grandes / complejos, verá que gastan MUCHOS recursos en esa dirección.

Dado que la concurrencia no es una parte de primera clase de la mayoría de los idiomas, los programadores de hoy tienen un punto ciego de por qué les sería útil. Esto solo se hará más evidente a medida que cada teléfono y reloj de pulsera se dirija hacia 1000 núcleos. Ve a los barcos con una herramienta de detector de carreras incorporada.

ValienteNuevoMoneda
fuente
2

Para Erlang es común tener un proceso por conexión u otra tarea. Entonces, por ejemplo, un servidor de transmisión de audio puede tener 1 proceso por usuario conectado.

Erlang VM está optimizado para manejar miles o incluso cientos de miles de procesos al hacer que los cambios de contexto sean muy baratos.

Zachary K
fuente
1

Conveniencia. Cuando comencé a hacer programación de subprocesos múltiples, hacía mucha simulación y desarrollo de juegos para divertirme. Descubrí que es muy conveniente simplemente desenrollar un hilo para cada objeto y dejar que haga lo suyo en lugar de procesar cada uno a través de un bucle. Si su código no se ve afectado por un comportamiento no determinista y no tiene colisiones, puede facilitar la codificación. Con el poder disponible para nosotros ahora, si tuviera que volver a eso, ¡me puedo imaginar fácilmente cortar un par de miles de hilos debido a tener suficiente poder de procesamiento y memoria para manejar tantos objetos discretos!

Brian Knoblauch
fuente
1

Un ejemplo simple para Erlang, que fue diseñado para la comunicación: transferir paquetes de red. Cuando realiza una solicitud http, es posible que tenga miles de paquetes TCP / IP. Agregue a esto que todos se conectan al mismo tiempo, y usted tiene su caso de uso.

Considere muchas aplicaciones utilizadas internamente por cualquier gran empresa para manejar sus pedidos o lo que sea que necesiten. Los servidores web no son lo único que necesita hilos.

Florian Margaine
fuente
-2

Aquí me vienen a la mente algunas tareas de renderizado. Si está haciendo una larga cadena de operaciones en cada píxel de una imagen, y si esas operaciones son paralelizables, incluso una imagen relativamente pequeña de 1024x768 está en el soporte de "cientos de miles".

Maximus Minimus
fuente
2
Hace algunos años, pasé unos años haciendo procesamiento de imágenes FLIR en tiempo real, procesando imágenes de 256x256 a 30 cuadros por segundo. A menos que tenga MUCHOS procesadores de HARDWARE y una FORMA INCONSÚTIL de particionar sus datos entre ellos, lo ÚLTIMO que desea hacer es agregar cambio de contexto, contención de memoria y almacenamiento de caché a los costos computacionales reales.
John R. Strohm
Depende del trabajo que se haga. Si todo lo que está haciendo es entregar un trabajo a una unidad de núcleo / ejecución de hardware, después de lo cual puede olvidarse de él (y tenga en cuenta que esta es la forma en que funcionan las GPU, por lo que este no es un escenario hipotético), entonces el enfoque es válido.
Maximus Minimus