Monitores y Paso de Mensajes
Monitores y Paso de Mensajes
Monitores y Paso de Mensajes
Paso de mensajes
Cuando los procesos interactúan unos con otros, se deben satisfacer dos requisitos
básicos: la sincronización y la comunicación. Los procesos tienen que sincronizarse
para cumplir la exclusión mutua; los procesos cooperantes pueden necesitar
intercambiar información. Un método posible para ofrecer ambas funciones es el paso
de mensajes. El paso de mensajes tiene la ventaja adicional de que se presta a ser
implementado en sistemas distribuidos, así como en sistemas multiprocesador y
monoprocesador de memoria compartida.
La funcionalidad real del paso de mensajes se ofrece, normalmente, por medio de
un par de primitivas:
send (destino, mensaje)
receive (origen, mensaje)
Este es el conjunto mínimo de operaciones necesario para que los procesos
puedan dedicarse al paso de mensajes. Un proceso envía información en forma de un
mensaje a otro proceso designado como destino. Un proceso recibe información
ejecutando la primitiva receive, que indica el proceso emisor (origen) y el mensaje.
Sincronización
La comunicación de un mensaje entre dos procesos implica cierto nivel de
sincronización entre ambos. El receptor no puede recibir un mensaje hasta que sea
enviado por otro proceso. Además, hace falta especificar qué le sucede a un proceso
después de ejecutar una primitiva send o receive.
Considérese en primer lugar la primitiva send. Cuando se ejecuta una primitiva
send en un proceso, hay dos posibilidades: O bien el proceso emisor se bloquea hasta
que se recibe el mensaje o no se bloquea. Análogamente, cuando un proceso ejecuta una
primitiva receive, hay dos posibilidades:
Si previamente se ha enviado algún mensaje, éste es recibido y continúa la
ejecución.
Si no hay ningún mensaje esperando entonces, o bien (a) el proceso se bloquea
hasta que llega un mensaje o (b) el proceso continúa ejecutando, abandonando el intento
de recepción.
Así pues, tanto el emisor como el receptor pueden ser bloqueantes o no
bloqueantes. Son habituales las siguientes tres combinaciones, aunque cualquier sistema
concreto implementa sólo una o dos combinaciones:
Envío bloqueante, recepción bloqueante: tanto el emisor como el receptor se
bloquean hasta que se entrega el mensaje; esta técnica se conoce como
rendezvous. Esta combinación permite una fuerte sincronización entre procesos.
Envío no bloqueante, recepción bloqueante: aunque el emisor puede
continuar, el receptor se bloquea hasta que llega el mensaje solicitado. Esta es,
probablemente, la combinación más útil. Permite que un proceso envíe uno o
más mensajes a varios destinos tan rápido como sea posible. Un proceso que
debe recibir un mensaje antes de poder hacer alguna función útil tiene que
bloquearse hasta que llegue el mensaje. Un ejemplo es el de un proceso servidor
que ofrezca un servicio o un recurso a otros procesos.
Envío no bloqueante, recepción no bloqueante: nadie debe esperar.
El send no bloqueante es la forma más natural para muchas tareas de
programación concurrente. Por ejemplo, si se usa para solicitar una operación de salida,
tal como una impresión, permite al proceso solicitante realizar la solicitud en forma de
mensaje y continuar. Un posible riesgo del send no bloqueante es que un error puede
llevar a una situación en la que el proceso genere mensajes repetidamente. Como no hay
bloqueo para hacer entrar en disciplina al proceso, esos mensajes pueden consumir
recursos del sistema, incluido tiempo del procesador y espacio en buffer, en detrimento
de otros procesos y del sistema operativo. Además, el send no bloqueante carga sobre el
programador el peso de determinar qué mensaje se ha recibido: Los procesos deben
emplear mensajes de respuesta para acusar La recepción de un mensaje.
Para la primitiva receive, la versión bloqueante parece ser la más natural para
muchas tareas de programación concurrente. En general, un proceso que solicita un
mensaje necesitará la información esperada antes de continuar. Sin embargo, si se
pierde un mensaje, un proceso receptor puede quedarse bloqueado indefinidamente.
Este problema puede resolverse por medio del receive no bloqueante. Sin embargo, el
riesgo de esta solución es que, si un mensaje se envía después de que un proceso haya
ejecutado el correspondiente receive, el mensaje se perderá. Otro método posible
consiste en permitir a un proceso comprobar si hay un mensaje esperando antes de
ejecutar un receive y en permitir a un proceso especificar más de un origen en una
primitiva receive. Esta última técnica resulta útil si un proceso espera mensajes de más
de un origen y puede continuar si llega cualquiera de estos mensajes.
Direccionamiento
Evidentemente, es necesario disponer de alguna forma de especificar en la
primitiva send qué proceso va a recibir el mensaje. De forma similar, la mayoría de las
implementaciones permiten a los procesos receptores indicar el origen del mensaje que
se va a recibir.
Los distintos esquemas para hacer referencia a los procesos en las primitivas send
y receive se encuadran dentro de dos categorías: direccionamiento directo e indirecto.
Con el direccionamiento directo, la primitiva send incluye una identificación específica
del proceso destino. La primitiva receive se puede gestionar de dos formas. Una
posibilidad requiere que el proceso designe explícitamente un proceso emisor. Así pues,
el proceso debe conocer de antemano de qué proceso espera un mensaje. Esto suele ser
eficaz para procesos concurrentes y cooperantes. En otros casos, sin embargo, es
imposible especificar el proceso de origen por anticipado. Un ejemplo es un proceso
servidor de impresoras, que aceptará mensajes de solicitud de impresión de cualquier
otro proceso. Para tales aplicaciones, una técnica más efectiva consiste en usar
direccionamiento implícito. En este caso, el parámetro origen de la primitiva receive
tendrá un valor de retomo cuando se haya realizado la operación de recepción.
El otro enfoque es el direccionamiento indirecto. En este caso, los mensajes no se
envían directamente del emisor al receptor, sino a una estructura de datos compartida
formada por colas que pueden guardar los mensajes temporalmente. Estas colas se
denominan generalmente buzones (mailboxes). De este modo, para que dos procesos se
comuniquen, uno envía mensajes al buzón apropiado y el otro los coge del buzón.
Una ventaja del direccionamiento indirecto es que se desacopla a emisor y
receptor, permitiendo una mayor flexibilidad en el uso de los mensajes. La relación
entre emisores y receptores puede ser uno a uno, de muchos a uno, de uno a muchos o
de muchos a muchos. Una relación uno a uno permite que se establezca un enlace
privado de comunicaciones entre dos procesos, lo que aísla su interacción de injerencias
erróneas de otros procesos. Una relación de muchos a uno resulta útil para interacciones
cliente/servidor, donde un proceso ofrece un servicio a un conjunto de procesos. En este
caso, el buzón se denomina puerto. Una relación uno a muchos permite un emisor y
muchos receptores; es útil para aplicaciones en las que un mensaje o alguna información
se difunda a un conjunto de procesos.