Inicio > Sistemas Operativos > Threads y Procesos

Threads y Procesos

En el post anterior veíamos la diferencia entre ejecutar tareas o procesos de forma concurrente y paralela. En este post vamos a ver: ¿Qué son los procesos? ¿Qué son los Threads? ¿Qué diferencias hay entre usar Threads o Procesos para agilizar las cosas? ¿En qué casos es más conveniente hacer uso de uno, o el otro o ambos?.

¿Qué son los procesos?

En la jerga de los sistemas operativos, básicamente se le llama proceso a aquel programa que ha pasado por todas las etapas de compilación y link-edición y se encuentra almacenado en memoria listo para poder ejecutarse. Los procesos entonces son unidades de ejecución en un sistema operativo, y como tal tienen una serie de estructuras de datos asociadas que guardan información ya sea de control, estado, acerca de sus ancestros, registros del procesador, área de memoria, stack, privilegios, etc. Dichas estructuras de datos suelen conocerse como PCB (Process Control Block) o Bloque de Control de Procesos, suelen variar en algunos campos según el sistema operativo en el cual se esté ejecutando un determinado proceso, pero en sí la estructura es básicamente la misma.

Como vimos en el post anterior, a veces es necesario ejecutar tareas/procesos de forma concurrente o paralela, para lo cual supongamos que contamos con un proceso, que en un determinado momento crea otro proceso (un proceso hijo) que se encargará de llevar a cabo una tarea de forma concurrente o paralela a la tarea padre. Generalmente lo que hace el sistema operativo en estos casos es crear un nuevo proceso en base al actual, al padre, para esto el sistema operativo debe mantener ahora dos copias (totalmente separadas, cada una con su propia área de memoria) de un mismo proceso, que claramente desde el momento en que el nuevo proceso es creado, variará en algunos aspectos en función del tiempo a medida que ambos se vayan ejecutando.

Supongamos que el proceso padre se encuentra ejecutando, y de repente necesita leer varias líneas de un archivo alojado en uno de los discos locales. Dependiendo de la política que utilice el panificador de procesos del sistema operativo, el proceso que acaba de pedir un recurso de entrada salida, se bloqueará a la espera de noticias acerca de lo que acaba de pedir, cediendo su lugar en el procesador a un nuevo proceso en espera. Supongamos que este proceso en espera es el hijo que el anterior había creado en su momento, entonces el sistema operativo lo quita de la cola de espera y le sede el procesador para que pueda trabajar.

Todo este conjunto de pasos para la administración de los procesos por parte del sistema operativo lleva un tiempo, que no es para nada despreciable, si bien se trata de que sí lo sea, esto consume tiempo que generalmente se suele conocer como Overhead del sistema operativo. El overhead habla del tiempo de ejecución consumido por el sistema operativo, y resulta que el proceso de transferir un proceso en ejecución a un estado bloqueado, y viceversa, implica un movimiento en las estructuras de datos mantenidas por el sistema opertivo correspondiente a los procesos involucrados (PCB, TSS o task state segment, etc.), este pasaje de datos es lo que en mayor medida genera este overhead del que venimos hablando.

¿Qué son los Threads o Procesos Livianos? ¿Qué diferencias hay entre usar Threads o Procesos para agilizar las cosas?

Con el tiempo, se vio que este tipo de estructuras, en algunas ocasiones podrían reducirse, de forma tal de disminuir el overhead generado por la creación de procesos y el cambio de contexto mencionado anteriormente, añadiendo al paquete alguna que otra útil prestación. Debido a esta y algunas otras necesidades, nacieron los Threads (Hilos) o Procesos Livianos, Livianos precisamente por que su estructura (TCB o Thread Control Block, Bloque de Control de Hilos) ahora es más liviana, contiene menos datos, pero no por que se hayan obviado, sino por que ahora gran cantidad de la porción de memoria de cada proceso se comparte con los Threads, esto quiere decir que los Threads van a vivir dentro de los procesos. Normalmente uno puede ver a un proceso como contenedor de hilos, en C/C++ un proceso trivial tiene un único hilo, main(), y luego los demás que el proceso, o mismo otros Threads, vayan creando a su gusto/necesidad. De esta manera, ahora los context switch pasan a ser context switch livianos, que van a ser más rápidos debido al tamaño de la estructura TCB más compacta que la de los procesos (PCB). Así, en algunos casos, es posible reducir el overhead del sistema operativo reemplazando la creación de procesos hijo, por Threads.

Pero esto no significa que los Threads reemplazaron a los procesos, muchas veces y por ciertas razones, es necesario crear procesos que ejecuten de forma concurrente/paralela en lugar de Threads. Un aspecto que puede dar una pista acerca de esto es el hecho de que cuando un proceso finaliza, sea cual sea la razón, los Threads que lleva dentro también finalizan, en cambio esto no sucede así con los procesos hijo.

¿En qué casos es más conveniente hacer uso de Threads, o Procesos emparentados o ambos?

Apache, el servidor HTTP más utilizado en la red por los proveedores de hosting y las demás empresas, posee dos versiones, una de estas maneja a sus clientes haciendo uso de hilos separados de ejecución (MPM Worker), y la otra hace uso de procesos emparentados (MPM Prefork). En este caso es visible la diferencia, la velocidad de respuesta se incrementa notablemente en el caso de la versión Worker, es mucho más ventajoso utilizar esta última en lugar de Prefork. A continuación hay un enlace hacia un benchmark de Apache Worker vs. Prefork:

http://www.camelrichard.org/apache-prefork-vs-worker

(en inglés)

En un pequeño resumen se puede ver para Prefork:
[…]
Transactions: 6045 hits
Availability: 100.00 %
Elapsed time: 300.38 secs
Data transferred: 0.25 MB
Response time: 0.50 secs
Transaction rate: 20.12 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 9.97
Successful transactions: 6045
Failed transactions: 0
Longest transaction: 10.13
Shortest transaction: 0.00
[…]

Y para Worker:
[…]
Transactions: 11024 hits
Availability: 100.00 %
Elapsed time: 300.24 secs
Data transferred: 0.46 MB
Response time: 0.27 secs
Transaction rate: 36.72 trans/sec
Throughput: 0.00 MB/sec
Concurrency: 9.91
Successful transactions: 11024
Failed transactions: 0
Longest transaction: 11.92
Shortest transaction: 0.00
[…]

La diferencia en velocidad es de aproximadamente el 55% para Worker por sobre Prefork, lo cual es lógico según lo planteado anteriormente.

Así como este hay varios ejemplos, pero también está el hecho en el cual es necesario utilizar procesos  emparentados, por ejemplo, un proceso en un determinado momento recibe la orden del usuario por medio del teclado de ejecutar un comando, por ejemplo el shell BASH de Linux, en donde uno escribe un determinado comando y este se ejecuta entregando un determinado resultado, esto suele hacerse creando un nuevo proceso hijo, al cual inmediatamente “se lo rellena” con el comando solicitado por el usuario, y este corre de forma concurrente/paralela a la consola o shell. Siguiendo con el ejemplo anterior de Apache, hay veces en las que debido a la naturaleza de las aplicaciones que este va a ejecutar, por ejemplo para utilizar Apache 2 con PHP5 como módulo, es necesario utilizar la versión Prefork, ya que PHP5 cómo módulo de apache no admite la versión Worker, para que esto sea así, sólo a modo informativo, se debe instalar la versión CGI/Fast CGI de PHP5.

Cómo estos hay muchos otros casos en donde dependiendo de las necesidades es más productivo utilizar Threads que Procesos emparentados y viceversa.

Bueno, espero que les haya sido interesante el tema, en el próximo post veré de plantear ejemplos de uso tanto de Threads como de Procesos emparentados en diferentes lenguajes, e iremos viendo que problemas se nos presentan en el uso e interacción de cada uno de estos.

Hasta el próximo Post =)

Javier.

Categorías:Sistemas Operativos Etiquetas: ,
  1. Aquiles Barreto
    14 mayo 2009 a las 10:16 AM

    Hola Javier, muy acertados tus comentarios. Saludos.
    Estudiante de Doctorado en Computación. Universidad Simón Bolívar. Venezuela.

    • javiersegura
      14 mayo 2009 a las 3:16 PM

      Muchas gracias Aquiles =)

      Con el tiempo trataré temas más puntuales en cuanto a la programación concurrente/paralela, es un tema que hoy en día debe poder manejar todo programador, para así poder aprovechar al máximo las nuevas tecnologías de multiprocesamiento, no solo en software de plataforma a nivel sistema operativo, sino también en el desarrollo de sistemas web, el uso de Ajax en conjunto con tecnologías de procesamiento en paralelo o concurrente, permite que las grandes aplicaciones de hoy en día puedan aprovechar al máximo las facilidades que le brinda el servidor en el cual se alojan, cosa que día a día veo cómo se desaprovecha.

      Saludos desde Argentina!

      Javier.

  2. 27 octubre 2012 a las 9:00 AM

    Buen Post
    Me agradó la manera en que relata sobre el tema.
    Seguiré volviendo esta web

  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: