Procesos e hilos¶
Definíamos el concepto de proceso como un flujo de ejecución con un contexto asociado:
- El identificador del proceso.
- El identificador del proceso padre.
- El usuario y grupo propietarios del proceso.
- El estado del proceso.
- El código que ejecuta el proceso.
- La pila del proceso.
- El contador de programa.
- Los apuntadores a las tablas de páginas del proceso.
- La tabla de descriptores de ficheros.
- El estado de las señales.
- La prioridad del proceso.
En un sistema multiprogramado, los procesos constituyen una forma de expresar concurrencia. Sin embargo el concepto de proceso tal como lo implementa Linux no fue desarrollado, hace más de medio siglo, para este fin, sino para permitir la convivencia de los programas de distintos usuarios sobre los carísimos computadores de la época. Estos programas también se ejecutaban en concurrencia, pero solo interaccionaban entre ellos en su competencia por el acceso a los recursos compartidos, por ejemplo para almacenar sus resultados en el disco. Sin embargo, enseguida se vio el potencial del modelo de procesos para implementar programas genuinamente concurrentes, donde los procesos cooperaban, más que competían, en el acceso a los recursos, por ejemplo en una aplicación de control donde los procesos que atendían entradas concurrentes de diferentes dispositivos accedían al disco para dejar los resultados.
Cuando, hacia el último cuarto del siglo XX, la informática se abrió a nuevos tipos de aplicaciones, como por ejemplo la edición de textos, pronto se vio que el modelo de procesos resultaba demasiado rígido, en particular porque los procesos no compartían memoria. Piensa en un editor de textos que debe realizar varias tareas concurrentemente en estrecha relación: lectura, maquetación, corrección ortográfica, copias de seguridad… todas ellas accediendo a un mismo espacio común, el texto editado, que se almacena en memoria. Compartir ese espacio es más ágil que andar comunicando explícitamente datos de un proceso a otro. Cuando esta necesidad se hizo evidente, los sistemas operativos introdujeron la posibilidad de compartir segmentos de memoria entre los procesos, donde se ubicaban las variables compartidas. Sin embargo, durante la década de 1980 se desarrolló como alternativa un nuevo modelo de programación más ágil, que incluía entidades de ejecución con un contexto propio muy ligero, los hilos o threads, que compartían las variables del proceso y podían comunicarse a través de ellas de forma natural. De esta forma, nuestro editor de texto podía implementarse como un único proceso con varios hilos de ejecución.
El contexto de un hilo es muy reducido:
- El identificador del hilo.
- El estado del hilo.
- Una pila para ejecutar la función que se asigna al hilo y almacenar las variables locales.
- El contador de programa.
- El estado de algunas señales.
Cambiar el contexto de un hilo a otro dentro del mismo proceso resulta muy rápido. Esto, unido a la agilidad que otorga el compartir las variables, hace de la programación multihilo (o multithread) la forma más eficiente y extendida hoy en día de expresar concurrencia.
En los sistemas operativos modernos el proceso queda relegado al papel de entidad de asignación de recursos (memoria, descriptores de ficheros, etc), mientras que el thread es la entidad de planificación. Es decir, el scheduler planifica threads, de acuerdo básicamente a los mismos criterios aplicados a los procesos. Linux no sigue este modelo… pero ya sabemos que Linux no es un sistema operativo moderno.
Aunque el sistema operativo incluye generalmente sus propias llamadas al sistema para programar con hilos, es preferible prescindir de ellas y usar una biblioteca estándar, como es el caso de la biblioteca pthreads. Linux incluye escaso soporte nativo para hilos. Por ejemplo, la llamada al sistema clone(2)
permite crear un flujo de ejecución nuevo que comparte el contexto con el proceso que la ejecuta, pero es incompatible con cualquier otro sistema y nadie la usa directamente.