stringtranslate.com

Hilo (informática)

Un proceso con dos hilos de ejecución, ejecutándose en un procesador .
Programa versus proceso versus
programación de subprocesos , preferencia y cambio de contexto

En informática , un hilo de ejecución es la secuencia más pequeña de instrucciones programadas que un programador , que normalmente forma parte del sistema operativo , puede gestionar de forma independiente . [1] En muchos casos, un hilo es un componente de un proceso .

Los múltiples subprocesos de un proceso determinado pueden ejecutarse simultáneamente (a través de capacidades de subprocesos múltiples), compartiendo recursos como la memoria , mientras que diferentes procesos no comparten estos recursos. En particular, los subprocesos de un proceso comparten su código ejecutable y los valores de sus variables asignadas dinámicamente y variables globales no locales de subprocesos en un momento dado.

La implementación de subprocesos y procesos difiere entre sistemas operativos. En Modern Operating Systems , Tanenbaum muestra que son posibles muchos modelos distintos de organización de procesos. [2] [ página necesaria ]

Historia

Los subprocesos hicieron una aparición temprana bajo el nombre de "tareas" en OS/360 Multiprogramming with a Variable Number of Tasks (MVT) en 1967. Saltzer (1966) atribuye a Victor A. Vyssotsky el término "thread". [3]

El uso de subprocesos en aplicaciones de software se volvió más común a principios de la década de 2000 cuando las CPU comenzaron a utilizar múltiples núcleos. Las aplicaciones que deseaban aprovechar múltiples núcleos para obtener ventajas de rendimiento debían emplear concurrencia para utilizar los múltiples núcleos. [4]

Conceptos relacionados

La programación se puede realizar a nivel de kernel o de usuario, y la multitarea se puede realizar de forma preventiva o cooperativa . Esto produce una variedad de conceptos relacionados.

Procesos

En el nivel del kernel, un proceso contiene uno o más subprocesos del kernel , que comparten los recursos del proceso, como memoria y identificadores de archivos; un proceso es una unidad de recursos, mientras que un subproceso es una unidad de programación y ejecución. La programación del kernel generalmente se realiza de manera uniforme, de manera preventiva o, con menos frecuencia, de manera cooperativa. A nivel de usuario, un proceso como un sistema de ejecución puede por sí mismo programar múltiples subprocesos de ejecución. Si estos no comparten datos, como en Erlang, generalmente se denominan de manera análoga procesos, [5] mientras que si comparten datos generalmente se les llama subprocesos (de usuario) , particularmente si se programan de manera preventiva. Los subprocesos de usuario programados de forma cooperativa se conocen como fibras ; diferentes procesos pueden programar hilos de usuario de manera diferente. Los subprocesos del usuario pueden ser ejecutados por los subprocesos del núcleo de varias maneras (uno a uno, muchos a uno, muchos a muchos). El término " proceso ligero " se refiere de diversas formas a subprocesos de usuario o a mecanismos del núcleo para programar subprocesos de usuario en subprocesos del núcleo.

Un proceso es una unidad "pesada" de programación del kernel, ya que crear, destruir y cambiar procesos es relativamente costoso. Procesa recursos propios asignados por el sistema operativo. Los recursos incluyen memoria (tanto para código como para datos), identificadores de archivos , sockets, identificadores de dispositivos, ventanas y un bloque de control de procesos . Los procesos están aislados mediante aislamiento de procesos y no comparten espacios de direcciones ni recursos de archivos, excepto a través de métodos explícitos, como heredar identificadores de archivos o segmentos de memoria compartida, o mapear el mismo archivo de forma compartida; consulte comunicación entre procesos . Crear o destruir un proceso es relativamente costoso, ya que se deben adquirir o liberar recursos. Los procesos suelen realizar múltiples tareas de forma preventiva y el cambio de procesos es relativamente costoso, más allá del costo básico del cambio de contexto , debido a problemas como el vaciado de caché (en particular, el cambio de procesos cambia el direccionamiento de la memoria virtual, lo que provoca la invalidación y, por lo tanto, el vaciado de un búfer de búsqueda de traducción sin etiquetar ( TLB), especialmente en x86).

Hilos del núcleo

Un subproceso del núcleo es una unidad "ligera" de programación del núcleo. Existe al menos un subproceso del núcleo dentro de cada proceso. Si existen varios subprocesos del kernel dentro de un proceso, comparten la misma memoria y recursos de archivos. Los subprocesos del kernel realizan múltiples tareas de forma preventiva si el programador de procesos del sistema operativo es preventivo. Los subprocesos del kernel no poseen recursos excepto una pila , una copia de los registros que incluye el contador del programa y el almacenamiento local del subproceso (si lo hay) y, por lo tanto, su creación y destrucción son relativamente económicas. El cambio de subprocesos también es relativamente económico: requiere un cambio de contexto (guardar y restaurar registros y puntero de pila), pero no cambia la memoria virtual y, por lo tanto, es compatible con el caché (dejando TLB válido). El kernel puede asignar uno o más subprocesos de software a cada núcleo de una CPU (pudiendo asignarse a sí mismo múltiples subprocesos de software dependiendo de su soporte para subprocesos múltiples) y puede intercambiar subprocesos que se bloqueen. Sin embargo, los subprocesos del kernel tardan mucho más que los subprocesos del usuario en intercambiarse.

Hilos de usuario

Los subprocesos a veces se implementan en bibliotecas del espacio de usuario , por lo que se denominan subprocesos de usuario . El kernel no los conoce, por lo que se administran y programan en el espacio de usuario . Algunas implementaciones basan sus subprocesos de usuario en varios subprocesos del kernel, para beneficiarse de las máquinas multiprocesador (modelo M:N). Los subprocesos de usuario implementados por máquinas virtuales también se denominan subprocesos verdes .

Como las implementaciones de subprocesos de usuario suelen estar completamente en el espacio de usuario , el cambio de contexto entre subprocesos de usuario dentro del mismo proceso es extremadamente eficiente porque no requiere ninguna interacción con el kernel: se puede realizar un cambio de contexto guardando localmente los registros de la CPU utilizados por el actualmente ejecutando el hilo o fibra del usuario y luego cargando los registros requeridos por el hilo o fibra del usuario para ser ejecutado. Dado que la programación se produce en el espacio del usuario, la política de programación se puede adaptar más fácilmente a los requisitos de la carga de trabajo del programa.

Sin embargo, el uso de bloqueo de llamadas al sistema en subprocesos de usuario (a diferencia de subprocesos del núcleo) puede resultar problemático. Si un subproceso de usuario o una fibra realiza una llamada al sistema que se bloquea, los demás subprocesos y fibras del usuario en el proceso no podrán ejecutarse hasta que regrese la llamada al sistema. Un ejemplo típico de este problema es al realizar E/S: la mayoría de los programas están escritos para realizar E/S sincrónicamente. Cuando se inicia una operación de E/S, se realiza una llamada al sistema y no regresa hasta que se haya completado la operación de E/S. Mientras tanto, todo el proceso es "bloqueado" por el kernel y no puede ejecutarse, lo que impide que otros subprocesos y fibras del usuario en el mismo proceso se ejecuten.

Una solución común a este problema (utilizada, en particular, por muchas implementaciones de subprocesos verdes) es proporcionar una API de E/S que implemente una interfaz que bloquee el subproceso que llama, en lugar de todo el proceso, mediante el uso de E/S sin bloqueo internamente. y programar otro hilo o fibra de usuario mientras la operación de E/S está en curso. Se pueden proporcionar soluciones similares para otras llamadas al sistema de bloqueo. Alternativamente, el programa se puede escribir para evitar el uso de E/S sincrónicas u otras llamadas al sistema de bloqueo (en particular, usando E/S sin bloqueo, incluidas continuaciones lambda y/o primitivas async/ await [6] ).

Fibras

Las fibras son una unidad de programación aún más ligera que se programa de forma cooperativa : una fibra en ejecución debe " ceder " explícitamente para permitir que otra fibra se ejecute, lo que hace que su implementación sea mucho más fácil que los subprocesos del núcleo o del usuario. Se puede programar una fibra para que se ejecute en cualquier hilo del mismo proceso. Esto permite que las aplicaciones obtengan mejoras de rendimiento al administrar la programación por sí mismas, en lugar de depender del programador del kernel (que puede no estar ajustado para la aplicación). Los entornos de programación paralela como OpenMP a veces implementan sus tareas a través de fibras. [7] [8] Estrechamente relacionadas con las fibras están las corrutinas , con la distinción de que las corrutinas son una construcción a nivel de lenguaje, mientras que las fibras son una construcción a nivel de sistema.

Hilos vs procesos

Los subprocesos se diferencian de los procesos tradicionales del sistema operativo multitarea en varios aspectos:

Se dice que sistemas como Windows NT y OS/2 tienen subprocesos baratos y procesos costosos ; en otros sistemas operativos no hay una diferencia tan grande excepto en el costo de un conmutador de espacio de direcciones , que en algunas arquitecturas (notablemente x86 ) resulta en un vaciado del búfer de traducción (TLB).

Las ventajas y desventajas de los subprocesos frente a los procesos incluyen:

Planificación

Programación preventiva vs cooperativa

Los sistemas operativos programan subprocesos de forma preventiva o cooperativa . Los sistemas operativos multiusuario generalmente prefieren el multiproceso preventivo por su control más preciso sobre el tiempo de ejecución a través del cambio de contexto . Sin embargo, la programación preventiva puede cambiar de contexto los subprocesos en momentos no previstos por los programadores, provocando así bloqueo de convoy , inversión de prioridad u otros efectos secundarios. Por el contrario, el multiproceso cooperativo se basa en que los subprocesos cedan el control de la ejecución, garantizando así que los subprocesos se ejecuten hasta su finalización . Esto puede causar problemas si un subproceso multitarea cooperativa se bloquea al esperar un recurso o si priva a otros subprocesos al no ceder el control de la ejecución durante un cálculo intensivo.

Sistemas de procesador único o multiprocesador

Hasta principios de la década de 2000, la mayoría de las computadoras de escritorio tenían solo una CPU de un solo núcleo, sin soporte para subprocesos de hardware , aunque los subprocesos todavía se usaban en dichas computadoras porque el cambio entre subprocesos era generalmente aún más rápido que los cambios de contexto de proceso completo . En 2002, Intel añadió soporte para multihilo simultáneo al procesador Pentium 4 , bajo el nombre de hyper-threading ; en 2005, introdujeron el procesador Pentium D de doble núcleo y AMD presentó el procesador Athlon 64 X2 de doble núcleo .

Los sistemas con un solo procesador generalmente implementan subprocesos múltiples mediante división de tiempo : la unidad central de procesamiento (CPU) conmuta entre diferentes subprocesos de software . Este cambio de contexto suele ocurrir con suficiente frecuencia como para que los usuarios perciban que los subprocesos o tareas se ejecutan en paralelo (para los sistemas operativos de servidor/escritorio populares, el intervalo de tiempo máximo de un subproceso, cuando otros subprocesos están esperando, a menudo se limita a 100-200 ms). En un sistema multiprocesador o multinúcleo , se pueden ejecutar varios subprocesos en paralelo , y cada procesador o núcleo ejecuta un subproceso separado simultáneamente; En un procesador o núcleo con subprocesos de hardware , también se pueden ejecutar subprocesos de software separados al mismo tiempo mediante subprocesos de hardware separados.

Modelos de roscado

1:1 (subprocesamiento a nivel de kernel)

Los subprocesos creados por el usuario en una correspondencia 1:1 con entidades programables en el kernel [9] son ​​la implementación de subprocesos más simple posible. OS/2 y Win32 utilizaron este enfoque desde el principio, mientras que en Linux la biblioteca GNU C implementa este enfoque (a través de NPTL o LinuxThreads anteriores ). Este enfoque también lo utilizan Solaris , NetBSD , FreeBSD , macOS e iOS .

M :1 (subprocesamiento a nivel de usuario)

Un modelo M :1 implica que todos los subprocesos a nivel de aplicación se asignan a una entidad programada a nivel de kernel; [9] el núcleo no tiene conocimiento de los subprocesos de la aplicación. Con este enfoque, el cambio de contexto se puede realizar muy rápidamente y, además, se puede implementar incluso en núcleos simples que no admiten subprocesos. Uno de los principales inconvenientes, sin embargo, es que no puede beneficiarse de la aceleración del hardware en procesadores multiproceso o en computadoras con múltiples procesadores : nunca se programa más de un subproceso al mismo tiempo. [9] Por ejemplo: si uno de los subprocesos necesita ejecutar una solicitud de E/S, todo el proceso se bloquea y no se puede utilizar la ventaja del subproceso. GNU Portable Threads utiliza subprocesos a nivel de usuario, al igual que State Threads .

M : N (roscado híbrido)

M : N asigna un número M de subprocesos de aplicación a un número N de entidades del núcleo, [9] o "procesadores virtuales". Este es un compromiso entre los subprocesos a nivel de kernel ("1:1") y a nivel de usuario (" N :1"). En general, los sistemas de subprocesos " M : N " son más complejos de implementar que los subprocesos del núcleo o del usuario, porque se requieren cambios tanto en el código del núcleo como en el espacio de usuario [ se necesita aclaración ] . En la implementación M:N, la biblioteca de subprocesos es responsable de programar subprocesos de usuario en las entidades programables disponibles; esto hace que el cambio de contexto de los subprocesos sea muy rápido, ya que evita llamadas al sistema. Sin embargo, esto aumenta la complejidad y la probabilidad de inversión de prioridad , así como una programación subóptima sin una coordinación extensa (y costosa) entre el programador del área de usuario y el programador del núcleo.

Ejemplos de implementación híbrida

Historia de los modelos de subprocesos en sistemas Unix.

SunOS 4.x implementó procesos livianos o LWP. NetBSD 2.x+ y DragonFly BSD implementan LWP como subprocesos del kernel (modelo 1:1). SunOS 5.2 a SunOS 5.8, así como NetBSD 2 a NetBSD 4 implementaron un modelo de dos niveles, multiplexando uno o más subprocesos a nivel de usuario en cada subproceso del kernel (modelo M:N). SunOS 5.9 y posteriores, así como NetBSD 5, eliminaron la compatibilidad con subprocesos de usuario, volviendo a un modelo 1:1. [10] FreeBSD 5 implementó el modelo M:N. FreeBSD 6 admitía tanto 1:1 como M:N, los usuarios podían elegir cuál debía usarse con un programa determinado usando /etc/libmap.conf. A partir de FreeBSD 7, el 1:1 se convirtió en el predeterminado. FreeBSD 8 ya no soporta el modelo M:N.

Programas de un solo subproceso frente a programas de subprocesos múltiples

En programación de computadoras , un solo subproceso es el procesamiento de un comando a la vez. [11] En el análisis formal de la semántica de las variables y el estado del proceso, el término subproceso único se puede usar de manera diferente para significar "retroceder dentro de un solo subproceso", lo cual es común en la comunidad de programación funcional . [12]

El multiproceso se encuentra principalmente en sistemas operativos multitarea. Multithreading es un modelo de programación y ejecución generalizado que permite que existan múltiples hilos dentro del contexto de un proceso. Estos subprocesos comparten los recursos del proceso, pero pueden ejecutarse de forma independiente. El modelo de programación por subprocesos proporciona a los desarrolladores una abstracción útil de la ejecución concurrente. El multiproceso también se puede aplicar a un proceso para permitir la ejecución paralela en un sistema multiprocesamiento .

Las bibliotecas de subprocesos múltiples tienden a proporcionar una llamada a función para crear un nuevo subproceso, que toma una función como parámetro. Luego se crea un hilo concurrente que comienza a ejecutar la función pasada y finaliza cuando la función regresa. Las bibliotecas de subprocesos también ofrecen funciones de sincronización de datos.

Hilos y sincronización de datos.

Los hilos del mismo proceso comparten el mismo espacio de direcciones. Esto permite que el código que se ejecuta simultáneamente se combine de manera estrecha y conveniente para intercambiar datos sin la sobrecarga o la complejidad de un IPC . Sin embargo, cuando se comparten entre subprocesos, incluso las estructuras de datos simples se vuelven propensas a condiciones de carrera si requieren más de una instrucción de CPU para actualizarse: dos subprocesos pueden terminar intentando actualizar la estructura de datos al mismo tiempo y encontrarla cambiando inesperadamente. Los errores causados ​​por las condiciones de carrera pueden ser muy difíciles de reproducir y aislar.

Para evitar esto, las interfaces de programación de aplicaciones (API) de subprocesos ofrecen primitivas de sincronización, como mutex, para bloquear estructuras de datos contra el acceso concurrente. En sistemas monoprocesador, un subproceso que se ejecuta en un mutex bloqueado debe suspenderse y, por lo tanto, desencadenar un cambio de contexto. En sistemas multiprocesador, el hilo puede sondear el mutex en un spinlock . Ambos pueden minar el rendimiento y forzar a los procesadores en sistemas de multiprocesamiento simétrico (SMP) a competir por el bus de memoria, especialmente si la granularidad del bloqueo es demasiado fina.

Otras API de sincronización incluyen variables de condición , secciones críticas , semáforos y monitores .

Grupos de subprocesos

Un patrón de programación popular que involucra subprocesos es el de grupos de subprocesos donde se crea una cantidad determinada de subprocesos al inicio que luego esperan a que se asigne una tarea. Cuando llega una nueva tarea, se despierta, la completa y vuelve a esperar. Esto evita las funciones relativamente costosas de creación y destrucción de subprocesos para cada tarea realizada y quita la administración de subprocesos de las manos del desarrollador de la aplicación y la deja a una biblioteca o al sistema operativo que sea más adecuado para optimizar la administración de subprocesos.

Pros y contras de los programas multiproceso frente a los programas de un solo subproceso

Las aplicaciones multiproceso tienen las siguientes ventajas frente a las de un solo subproceso:

Las aplicaciones multiproceso tienen los siguientes inconvenientes:

Soporte de lenguaje de programación

Muchos lenguajes de programación admiten subprocesos de alguna manera.

Ver también

Referencias

  1. ^ Lamport, Leslie (septiembre de 1979). "Cómo hacer una computadora multiprocesador que ejecute correctamente programas multiproceso" (PDF) . Transacciones IEEE en computadoras . C-28 (9): 690–691. doi :10.1109/tc.1979.1675439. S2CID  5679366.
  2. ^ Tanenbaum, Andrew S. (1992). Sistemas operativos modernos . Ediciones internacionales Prentice-Hall. ISBN 0-13-595752-4.
  3. ^ Saltzer, Jerome Howard (julio de 1966). Control de tráfico en un sistema informático multiplexado (PDF) (tesis de Doctorado en Ciencias). pag. 20.
  4. ^ Sutter, Herb (marzo de 2005). "Se acabó el almuerzo gratis: un giro fundamental hacia la concurrencia en el software". Diario del Dr. Dobb . 30 (3).
  5. ^ "Erlang: 3.1 Procesos".
  6. ^ Ignatchenko, Sergey. Ocho formas de manejar devoluciones sin bloqueo en programas de paso de mensajes: desde C++98 pasando por C++11 hasta C++20. CPPCON. Archivado desde el original el 25 de noviembre de 2020 . Consultado el 24 de noviembre de 2020 .{{cite AV media}}: Mantenimiento CS1: bot: estado de la URL original desconocido ( enlace )
  7. ^ Ferat, Manuel; Pereira, Romain; Roussel, Adrián; Carribault, Patricio; Steffenel, Luiz-Angelo; Gautier, Thierry (septiembre de 2022). "Mejora de las aplicaciones basadas en tareas MPI+OpenMP para arquitecturas heterogéneas con soporte para GPU" (PDF) . OpenMP en un mundo moderno: del soporte multidispositivo a la metaprogramación . IWOMP 2022: 18º Taller Internacional sobre OpenMP. Apuntes de conferencias sobre informática. vol. 13527, págs. 3-16. doi :10.1007/978-3-031-15922-0_1. ISBN 978-3-031-15921-3. S2CID  251692327.
  8. ^ Iwasaki, Shintaro; Amer, Abdelhalim; Taura, Kenjiro; Seo, Sangmin; Balaji, Pavan. BOLT: Optimización de regiones paralelas de OpenMP con subprocesos a nivel de usuario (PDF) . La 28ª Conferencia Internacional sobre Arquitecturas Paralelas y Técnicas de Compilación.
  9. ^ abcd Silberschatz, Abraham ; Galvin, Peter Baer; Gagne, Greg (2013). Conceptos de sistemas operativos (9ª ed.). Hoboken, Nueva Jersey: Wiley. págs. 170-171. ISBN 9781118063330.
  10. ^ "Multiproceso en el entorno operativo Solaris" (PDF) . 2002. Archivado desde el original (PDF) el 26 de febrero de 2009.
  11. ^ Menéndez, Raúl; Lowe, Doug (2001). CICS de Murach para el programador COBOL. Mike Murach y asociados. pag. 512.ISBN _ 978-1-890774-09-7.
  12. ^ O'Hearn, Peter William; Tennent, RD (1997). Lenguajes tipo ALGOL. vol. 2. Birkhäuser Verlag . pag. 157.ISBN _ 978-0-8176-3937-2.
  13. ^ Ignatchenko, Sergey (agosto de 2010). "Un solo subproceso: ¿Regreso al futuro?". Sobrecarga . ACU (97): 16-19.
  14. ^ ab Lee, Edward (10 de enero de 2006). "El problema de los hilos". UC Berkeley.
  15. ^ Ignatchenko, Sergey (agosto de 2015). "El subproceso múltiple a nivel de lógica empresarial se considera perjudicial". Sobrecarga . ACU (128): 4–7.
  16. ^ Liebre 'No Bugs' (12 de septiembre de 2016). "Costos de operación en ciclos de reloj de la CPU".

Otras lecturas