thread multi definicion multithreading io blocking nonblocking

multithreading - multi - ¿La E/S no bloqueante es realmente más rápida que la E/S de bloqueo de múltiples subprocesos? ¿Cómo?



thread join python (7)

Actualmente estoy en el proceso de implementar async io en una plataforma incrustada usando protothreads. Non blocking io hace la diferencia entre correr a 16000 fps y 160 fps. El mayor beneficio del no bloqueo es que puede estructurar su código para hacer otras cosas mientras el hardware hace su trabajo. Incluso la inicialización de dispositivos se puede hacer en paralelo.

Martín

Busqué en la web algunos detalles técnicos sobre el bloqueo de E / S y las E / S no bloqueantes y encontré varias personas que indicaban que la E / S no bloqueante sería más rápida que el bloqueo de E / S. Por ejemplo en este documento .

Si uso bloqueo de E / S, entonces, por supuesto, el hilo que está actualmente bloqueado no puede hacer nada más ... Porque está bloqueado. Pero tan pronto como un hilo comienza a bloquearse, el sistema operativo puede cambiar a otro hilo y no volver atrás hasta que haya algo que hacer para el hilo bloqueado. Entonces, mientras haya otro hilo en el sistema que necesite CPU y no esté bloqueado, no debería haber más tiempo de inactividad de la CPU en comparación con un enfoque basado en eventos sin bloqueo, ¿verdad?

Además de reducir el tiempo que la CPU está inactiva, veo una opción más para aumentar el número de tareas que una computadora puede realizar en un marco de tiempo determinado: Reduzca la sobrecarga introducida al cambiar los hilos. Pero, ¿cómo se puede hacer esto? ¿Y la sobrecarga es lo suficientemente grande como para mostrar efectos cuantificables? Aquí hay una idea sobre cómo puedo imaginarlo funcionando:

  1. Para cargar el contenido de un archivo, una aplicación delega esta tarea en un marco de E / S basado en eventos, pasando una función de devolución de llamada junto con un nombre de archivo
  2. El marco de eventos delega en el sistema operativo, que programa un controlador DMA del disco duro para escribir el archivo directamente en la memoria
  3. El marco de eventos permite que se ejecute más código.
  4. Una vez completada la copia de disco a memoria, el controlador DMA causa una interrupción.
  5. El controlador de interrupción del sistema operativo notifica al marco de E / S basado en eventos que el archivo está completamente cargado en la memoria. ¿Como hace eso? ¿Usando una señal?
  6. El código que se ejecuta actualmente dentro del marco de E / S del evento finaliza.
  7. El marco de E / S basado en eventos comprueba su cola y ve el mensaje del sistema operativo desde el paso 5 y ejecuta la devolución de llamada que obtuvo en el paso 1.

¿Así es como funciona? Si no es así, ¿cómo funciona? Eso significa que el sistema de eventos puede funcionar sin tener que tocar explícitamente la pila (como un programador real que necesitaría hacer una copia de seguridad de la pila y copiar la pila de otra cadena en la memoria mientras se cambian los hilos). ¿Cuánto tiempo ahorra esto realmente? ¿Hay más?


La E / S incluye múltiples tipos de operaciones, como leer y escribir datos de discos duros, acceder a recursos de red, llamar a servicios web o recuperar datos de bases de datos. Dependiendo de la plataforma y del tipo de operación, la E / S asíncrona usualmente aprovechará cualquier hardware o soporte de bajo nivel del sistema para realizar la operación. Esto significa que se realizará con el menor impacto posible en la CPU.

En el nivel de la aplicación, la E / S asíncrona impide que los hilos tengan que esperar a que se completen las operaciones de E / S. Tan pronto como se inicia una operación de E / S asincrónica, libera el hilo en el que se lanzó y se registra una devolución de llamada. Cuando la operación finaliza, la devolución de llamada se pone en cola para su ejecución en la primera cadena disponible.

Si la operación de E / S se ejecuta de forma síncrona, mantiene el hilo en ejecución sin hacer nada hasta que la operación se complete. El tiempo de ejecución no sabe cuándo se completa la operación de E / S, por lo que periódicamente proporcionará tiempo de CPU al hilo de espera, tiempo de CPU que de otro modo podría haber sido utilizado por otros subprocesos que tienen operaciones de CPU vinculadas a realizar.

Por lo tanto, como se mencionó en @ user1629468, las E / S asíncronas no ofrecen un mejor rendimiento sino una mejor escalabilidad. Esto es obvio cuando se ejecuta en contextos que tienen un número limitado de subprocesos disponibles, como es el caso de las aplicaciones web. La aplicación web generalmente usa un grupo de subprocesos desde el que asignan subprocesos a cada solicitud. Si las solicitudes están bloqueadas en operaciones de E / S de larga ejecución, existe el riesgo de agotar el grupo web y hacer que la aplicación web se congele o demore en responder.

Una cosa que he notado es que las E / S asíncronas no son la mejor opción cuando se trata de operaciones de E / S muy rápidas. En ese caso, el beneficio de no mantener un hilo ocupado mientras se espera que la operación de E / S se complete no es muy importante y el hecho de que la operación se inicie en un hilo y se complete en otro agrega una sobrecarga a la ejecución general.

Puede leer una investigación más detallada que he realizado recientemente sobre el tema de E / S asíncronas versus multitomadas here .


La mayor ventaja de las E / S no bloqueantes o asíncronas es que su hilo puede continuar su trabajo en paralelo. Por supuesto, puede lograr esto también utilizando un hilo adicional. Como dijiste para el mejor rendimiento general (del sistema), creo que sería mejor usar E / S asíncronas y no múltiples hilos (para reducir la conmutación de hilos).

Veamos posibles implementaciones de un programa de servidor de red que debe manejar 1000 clientes conectados en paralelo:

  1. Un hilo por conexión (puede bloquear E / S, pero también puede ser E / S sin bloqueo).
    Cada hilo requiere recursos de memoria (¡también memoria del kernel!), Eso es una desventaja. Y cada hilo adicional significa más trabajo para el programador.
  2. Un hilo para todas las conexiones.
    Esto toma carga del sistema porque tenemos menos hilos. Pero también le impide utilizar todo el rendimiento de su máquina, ya que puede terminar conduciendo un procesador al 100% y dejando que todos los demás procesadores permanezcan inactivos.
  3. Algunos hilos donde cada hilo maneja algunas de las conexiones.
    Esto toma carga del sistema porque hay menos hilos. Y puede usar todos los procesadores disponibles. En Windows, este enfoque es compatible con la API Thread Pool .

Por supuesto, tener más hilos no es un problema en sí mismo. Como habrás reconocido, elegí una gran cantidad de conexiones / hilos. Dudo que veas ninguna diferencia entre las tres posibles implementaciones si hablamos de solo una docena de hilos (esto también es lo que Raymond Chen sugiere en la publicación de blog de MSDN ¿Tiene Windows un límite de 2000 hilos por proceso? ).

En Windows, el uso de archivos no almacenados en E / S significa que las escrituras deben ser de un tamaño que sea un múltiplo del tamaño de la página. No lo he probado, pero parece que esto también podría afectar el rendimiento de escritura positivamente para las escrituras síncronas y asíncronas almacenadas en búfer.

Los pasos 1 a 7 que describes dan una buena idea de cómo funciona. En Windows, el sistema operativo le informará acerca de la finalización de una E / S asincrónica ( WriteFile con estructura OVERLAPPED ) utilizando un evento o una devolución de llamada. Las funciones de devolución de llamada solo serán llamadas, por ejemplo, cuando su código llame a WaitForMultipleObjectsEx con bAlertable establecido en true .

Algunas lecturas más en la web:


La mejora hasta donde yo sé es que Asynchronous I / O usa (estoy hablando de MS System, solo para aclarar) los llamados puertos de E / S de finalización . Al usar la llamada Asincrónica, el marco aprovecha dicha arquitectura automáticamente, y se supone que es mucho más eficiente que el mecanismo de enhebrado estándar. Como experiencia personal, puedo decir que sentirá sensiblemente que su aplicación es más reactiva si prefiere las AsyncCalls en lugar de bloquear las conversaciones.


La razón principal para usar AIO es para la escalabilidad. Cuando se ven en el contexto de algunos hilos, los beneficios no son obvios. Pero cuando el sistema escala a miles de hilos, AIO ofrecerá un rendimiento mucho mejor. La advertencia es que la biblioteca de AIO no debería introducir más cuellos de botella.


Para suponer una mejora de velocidad debido a cualquier forma de multi-computación, debe presumir que múltiples tareas basadas en CPU se ejecutan simultáneamente sobre múltiples recursos informáticos (generalmente núcleos de procesador) o que no todas las tareas dependen del uso concurrente de el mismo recurso, es decir, algunas tareas pueden depender de un subcomponente de sistema (almacenamiento de disco, por ejemplo) mientras que algunas tareas dependen de otra (recibir comunicación de un dispositivo periférico) y otras pueden requerir el uso de núcleos de procesador.

El primer escenario a menudo se conoce como programación "paralela". El segundo escenario a menudo se denomina programación "concurrente" o "asincrónica", aunque "concurrente" algunas veces también se usa para referirse al caso de simplemente permitir que un sistema operativo entrelaje la ejecución de múltiples tareas, independientemente de si dicha ejecución debe tomarse coloque en serie o si se pueden usar múltiples recursos para lograr la ejecución en paralelo. En este último caso, "concurrente" generalmente se refiere a la forma en que se escribe la ejecución en el programa, y ​​no desde la perspectiva de la simultaneidad real de la ejecución de la tarea.

Es muy fácil hablar de todo esto con suposiciones tácitas. Por ejemplo, algunos son rápidos para hacer una afirmación como "E / S asincrónica será más rápida que E / S de múltiples subprocesos". Esta afirmación es dudosa por varias razones. En primer lugar, podría darse el caso de que un determinado marco asíncrono de E / S se implemente con precisión con multi-threading, en cuyo caso son uno en el mismo y no tiene sentido decir que un concepto "es más rápido que" el otro .

En segundo lugar, incluso en el caso en que exista una implementación de subproceso único de un marco asíncrono (como un bucle de evento de subproceso único), debe hacer una suposición sobre lo que está haciendo ese bucle. Por ejemplo, una cosa tonta que puede hacer con un bucle de eventos de un solo subproceso es pedirle que complete asincrónicamente dos tareas diferentes puramente vinculadas a la CPU. Si hicieras esto en una máquina con solo un núcleo de procesador único idealizado (ignorando las optimizaciones de hardware modernas) entonces realizar esta tarea "asincrónicamente" no funcionaría de manera diferente a realizarlo con dos subprocesos administrados independientemente, o con solo un proceso en solitario: - la diferencia puede deberse a la conmutación del contexto del subproceso o a las optimizaciones del cronograma del sistema operativo, pero si ambas tareas van a la CPU, sería similar en ambos casos.

Es útil imaginar muchos de los casos de esquina inusuales o estúpidos con los que te puedes encontrar.

"Asíncrono" no tiene que ser concurrente, por ejemplo, como se indicó anteriormente: ejecuta "asincrónicamente" dos tareas vinculadas a la CPU en una máquina con exactamente un núcleo de procesador.

La ejecución multiproceso no tiene que ser simultánea: engendras dos hilos en una máquina con un único núcleo de procesador, o pides dos hilos para adquirir cualquier otro tipo de recurso escaso (imagina, por ejemplo, una base de datos de red que solo puede establecer una conexión a la vez). La ejecución de los hilos puede intercalarse, sin embargo, el planificador del sistema operativo lo considera oportuno, pero su tiempo de ejecución total no se puede reducir (y aumentará desde el cambio de contexto del hilo) en un único núcleo (o más en general, si genera más hilos que núcleos para ejecutarlos, o tienen más hilos que piden un recurso de lo que el recurso puede mantener). Lo mismo ocurre con el procesamiento múltiple también.

Por lo tanto, ni E / S asíncronas ni multithreading tienen que ofrecer ninguna ganancia de rendimiento en términos de tiempo de ejecución. Incluso pueden ralentizar las cosas.

Sin embargo, si define un caso de uso específico, como un programa específico que hace una llamada de red para recuperar datos de un recurso conectado a la red como una base de datos remota y también realiza un cálculo local vinculado a la CPU, puede comenzar a razonar sobre las diferencias de rendimiento entre los dos métodos dado un supuesto particular sobre el hardware.

Las preguntas para hacer: ¿Cuántos pasos computacionales necesito realizar y cuántos sistemas independientes de recursos hay para realizarlos? ¿Existen subconjuntos de los pasos computacionales que requieren el uso de subcomponentes de sistemas independientes y pueden beneficiarse al hacerlo simultáneamente? ¿Cuántos núcleos de procesador tengo y cuál es la sobrecarga para usar múltiples procesadores o hilos para completar tareas en núcleos separados?

Si sus tareas dependen en gran medida de subsistemas independientes, entonces una solución asincrónica podría ser buena. Si el número de subprocesos necesarios para manejarlo fuera grande, de modo que el cambio de contexto no fuera trivial para el sistema operativo, entonces una solución asíncrona de subproceso único podría ser mejor.

Siempre que las tareas estén vinculadas por el mismo recurso (por ejemplo, múltiples necesidades para acceder concurrentemente a la misma red o recurso local), entonces el multi-threading probablemente introducirá una sobrecarga insatisfactoria, y mientras que la asincronía de un único subproceso puede introducir menos sobrecarga, en tal recurso- situación limitada tampoco puede producir una aceleración. En tal caso, la única opción (si desea una aceleración) es hacer disponibles múltiples copias de ese recurso (por ejemplo, múltiples núcleos de procesador si el recurso escaso es CPU, una mejor base de datos que admite más conexiones concurrentes si el recurso escaso es una base de datos de conexión limitada, etc.).

Otra forma de decirlo es: permitir que el sistema operativo intercale el uso de un solo recurso para dos tareas no puede ser más rápido que simplemente dejar que una tarea use el recurso mientras la otra espera, y luego dejar que la segunda tarea termine en serie. Además, el costo del planificador de los medios de entrelazado en cualquier situación real, en realidad crea una desaceleración. No importa si se produce el uso intercalado de la CPU, un recurso de red, un recurso de memoria, un dispositivo periférico o cualquier otro recurso del sistema.


Una posible implementación de E / S sin bloqueo es exactamente lo que ha dicho, con un conjunto de hilos de fondo que bloquean las E / S y notifican el hilo del autor de la E / S a través de algún mecanismo de devolución de llamada. De hecho, así es como funciona el módulo AIO en glibc. Here hay algunos detalles vagos sobre la implementación.

Si bien esta es una buena solución que es bastante portátil (siempre que tenga subprocesos), el sistema operativo suele ser capaz de dar servicio a E / S sin bloqueo de manera más eficiente. Este artículo de Wikipedia enumera posibles implementaciones además del grupo de subprocesos.