Vous êtes sur la page 1sur 37

El lenguaje C++ Concurrente

Jos Oscar Olmedo Aguirre *

Centro de Investigacin y de Estudios Avanzados del IPN Departamento de Ingeniera Elctrica Seccin de Computacin

Resumen El propsito de este trabajo es hacer una sinopsis del lenguaje C++ Concurrente desarrollado por el autor. El lenguaje C++ Concurrente es una extensin al lenguaje C++ basada en el modelo de Proceso Distribuido de Brinch Hansen [3] que hace posible la programacin concurrente orientada a objetos. Adems, se describen y resuelven los problemas que describe Brinch Hansen en su artculo usando el C++ Concurrente. Los programas fueron completamente probados en el prototipo del compilador desarrollado para l.

Palabras clave: Programacin concurrente, programacin orientada a objetos, lenguajes de programacin, no determinismo, clases, objetos, procesos, comunicacin, sincronizacin, cooperacin, regiones crticas condicionales, semforos. ------------* Profesor de la Seccin de Computacin del Departamento de Ingeniera Elctrica.

PREFACIO La programacin orientada a objetos es un modelo de programacin que plantea la descomposicin de sistemas en un conjunto de entidades distinguibles llamados objetos, los cuales poseen un estado o configuracin interna. El estado de un objeto se caracteriza por una coleccin de propiedades medibles que le es propia. El estado del objeto se puede transformar por la aplicacin de ciertas operaciones, las cuales constituyen el nico medio de interaccin con el exterior. La programacin concurrente es una metodologa para tratar con problemas que involucran mltiples entidades activas desarrollndose con un paralelismo potencial. Las entidades activas o procesos poseen un comportamiento propio que se manifiesta por la ejecucin de procedimientos lo que les permite interactuar con otros procesos. Los procesos corresponden con nuestra nocin usual de programas secuenciales. La programacin concurrente orientada a objetos unifica los conceptos de objeto y proceso con un enfoque ms amplio. Sin embargo, de acuerdo con sus caractersticas se pueden distinguir dos tipos de objetos de acuerdo con su comportamiento en pasivos y activos. Los objetos pasivos corresponden con la nocin usual de coleccin de atributos y operaciones. Los objetos activos extienden a los objetos pasivos hasta considerarlos autnticos programas secuenciales. An cuando la programacin concurrente tiene sus orgenes en el estudio de los sistemas operativos es necesario distinguirlos. La tendencia es aumentar el grado de abstraccin de estos conceptos hasta convertirlos en construcciones de un lenguaje de programacin. Este trabajo es una recapitulacin de mi tesis de maestra: el lenguaje PL/M-86 Concurrente [8], una extensin al lenguaje estructurado por bloques, PL/M-86, para incluir objetos y procesos. Dicho lenguaje fue un primer intento por conjuntarlos. Sin embargo, los objetos que se lograron implantar eran muy simples porque slo permitan encapsular datos y procedimientos, caractersticas indispensables de los tipos abstractos de datos. En el lenguaje C++ Concurrente parto de un lenguaje orientado a objetos, el C++, el sucesor ms notable del lenguaje C. El lenguaje C++ permite escribir programas con un grado de abstraccin mayor, facilitando as la extensin hacia la concurrencia.

El contenido de este artculo es como sigue: la primera seccin describe las extensiones del lenguaje C++ Concurrente, la segunda seccin analiza el cambio de contexto entre procesos y corrutinas. Finalmente, la tercera seccin presenta los ejemplos que analiza Brinch Hansen en su artculo [3] escritos en el C++ Concurrente. Deseo agradecer al M. en C. Jorge Buenabad Chvez por los valiosos comentarios y sugerencias que hizo a este trabajo.

1. SINOPSIS DEL LENGUAJE C++ CONCURRENTE Los modelos dinmicos consideran que un sistema se encuentra formado por una coleccin de objetos autnomos que interactuan entre s. Los objetos del sistema son inherentemente concurrentes y poseen un estado que puede cambiar por su interaccin con otros objetos. Las interacciones ocurren cuando se usan ciertos procedimientos o servicios que ofrece o solicita el objeto. Las reglas de como se deben realizar dichas interacciones constituyen el modelo de programacin en el que se basa el sistema. El modelo de Proceso distribuido tiene el mrito de su generalidad y sencillez. La generalidad se manifiesta por su capacidad para describir una extensa variedad de conceptos en forma unificada: Procedimientos Corrutinas Procesos Clases Monitores Semforos Recipientes (buffers) Administradores de recursos Trayectorias (path expressions)

Su sencillez se debe a los pocos y eficaces mecanismos para la sincronizacin y la comunicacin entre procesos, lo que por otra parte, simplifica enormemente la implementacin. El lenguaje C++ Concurrente fu diseado e implementado por el autor siguiendo la direccin apuntada en su Tesis de maestra [8]. El C++ Concurrente es una extensin al lenguaje C++ que difiere con el enfoque propuesto en otros trabajos como el C Concurrente de Gehani y Roome, basado en el concepto de encuentro extendido [6] una extensin del encuentro (rendesvouz) de Ada [5], la implantacin de corrutinas hecha en los Laboratorios Bell como se describe en [13]. La diferencia esencial radica en el modelo de programacin en el que se basa nuestro lenguaje. En esta seccin presentaremos y discutiremos las extensiones del lenguaje. 1.0. Caractersticas del lenguaje C++ Concurrente 4

Las caractersticas ms relevantes del modelo en el que se basa el lenguaje C++ Concurrente se enumeran a continuacin: 1. Abstraccin de datos mediante clases (C++). 2. Creacin y destruccin esttica y dinmica de procesos y corrutinas (extensin). 3. Topologa esttica y dinmica de procesos (extensin). 4. Programacin en el estilo de proceso distribuido (extensin). 5. Ejecucin no determinstica de programas (extensin). 6. Comunicacin sncrona bidireccional (consecuencia). 7. Comunicacin por canales (extensin). 8. Compilacin separada de mdulos y facilidades de depuracin (ambiente). 9. Caracterizacin del sistema operativo como un proceso prestador de servicios. El lenguaje cuenta con un preprocesador construido sobre el lenguaje C++ de Borland versin 1.0 para mquinas basadas en el 8086 en el modelo de memoria small. El prototipo aprovecha las caractersticas del lenguaje as como las de su preprocesador para introducir el no determinismo que proveen los comandos protegidos con guardias. Un hecho notable de la implantacin del preprocesador es la de crear la ilusin de que se programa en un lenguaje que posee nuevas construcciones que son consistentes con el lenguaje C++ sin necesidad de usar un preprocesador externo. An cuando se construyen varias estructuras de datos y de control a tiempo de ejecucin, el cdigo generado posee un rendimiento bastante aceptable. Esto se debe a que las estructuras se construyen una sola vez, cuando se necesitan usar. Por otra parte, tiene la ventaja de probar las posibilidades que brinda el lenguaje antes de intentar realizar la construccin de un compilador. Esta decisin tiene consecuencias favorables que tiene sobre la facilidad de uso, la disponibilidad o la portabilidad (de los programas hechos en el C++). En esta versin no se incluye: 1. Manejo de excepciones. 2. Manejo de interrupciones. 3. Mtodos de verificacin de especificaciones.

En este reporte, tampoco se consideraron aspectos tan importantes como la medida del rendimiento, las arquitecturas o las redes de computadoras para procesamiento distribuido, el asignamiento de un procesador en ambientes de multiprocesamiento. 1.1. Palabras reservadas El lenguaje C++ Concurrente incluye las siguientes palabras reservadas a la lista del lenguaje C++: process nullprocess select when

1.2. Especificacin de procesos La especificacin de una clase de procesos establece su estructura interna y su comportamiento mediante la declaracin de sus componentes variables o funciones. Un proceso es un verdadero programa secuencial que hereda de la clase Process, la capacidad de representar el flujo de control que posee. Cada proceso posee una pila propia para guardar el estado de su ejecucin incluyendo el contenido de los registros del procesador y el rea de almacenamiento de las variables locales, entre otros. La especificacin de cualquier clase de proceso debe incluir al menos un constructor. La especificacin de un proceso introduce un tipo definido por el programador que es consistente con los tipos provistos por el lenguaje (p.ej. los tipos bsicos). Por ejemplo, la clase especial de procesos Writer, se especifica as: class Writer: Process{ int c; public: Writer(char*, int); }; Esta especificacin permite declarar variables de tipo Writer, as como apuntadores, arreglos y referencias. 1.3. Definicin de procesos La definicin de un proceso la forma el cuerpo de la funcin constructora. El comportamiento, generalmente no determinstico del proceso, lo 6

establece el texto de esta funcin. Diferentes funciones constructoras indicarn diferentes comportamientos de los procesos de la misma clase. Un proceso inicia su ejecucin con el primer estatuto del bloque y termina luego de ejecutar el ltimo estatuto que aparece en l. En otras palabras, la duracin del proceso es la misma que la del bloque que lo define. El comportamiento de la clase de procesos Writer se puede formular de la siguiente forma:
Writer::Writer(char* s, int n){ cout << "Proceso " << s << " inicia su actividad\n"; for(c = 0; c < n; c++){ cout << s << c; suspend(); // Cede el control a otro proceso } cout << "Proceso " << s << " termina su actividad\n"; }

Aunque las reglas de alcance de los identificadores del C++ se conservan en el C++ Concurrente, las reglas de duracin se han modificado para las variables locales declaradas en las funciones constructoras: 1. Las variables con atributo static se caracterizan por: (a) retener su valor, y (b) ser compartida por los procesos creados mediante la misma funcin constructora. 2. Las variables con atributo auto y los parmetros de la funcin constructora tienen la misma duracin del proceso. Esta modificacin en la regla de duracin permite al proceso conservar el estado de su procesamiento. El programador debe cuidar de que no ocurra ningn cambio de contexto antes de que el proceso haya iniciado sus variables de estado. 1.4. Especificacin del tamao de la pila Un proceso de cualquier clase cuando se construye tiene un tamao de pila estndar. Sin embargo, este tamao puede cambiarlo el programador, si as lo desea, en la definicin del constructor de su clase, invocando explcitamente al constructor de la clase base Process. Por ejemplo, si la clase Writer, no necesita demasiado espacio en la pila para su operacin, entonces puede reducirse el tamao de la pila indicando esto en el constructor de la clase base Process: 7

Writer::Writer(char* s, int n): Process(128){ cout << "Proceso " << s << " inicia su actividad\n"; ... cout << "Proceso " << s << " termina su actividad\n"; }

que asigna a todos los procesos de esta clase creados con este constructor, un tamao de pila de 128 palabras. Observe que la diferencia con el anterior constructor de Writer es la presencia de la expresin :Process(128) en el encabezado de la funcin. En el C++ Concurrente, es posible que un proceso no requiera de su pila. Esto ocurre cuando el proceso es un objeto en el sentido usual que se maneja en el lenguaje C++, es decir, su comportamiento se reduce a iniciar las variables de estado del proceso y no realiza ningn actividad posterior. Para especificarlo, se debe indicar que el proceso tiene una pila de tamao cero. 1.5. Especificacin de prioridad La poltica de administacin de procesos se base en las siguientes reglas: 1. Un proceso de mayor prioridad se selecciona en preferencia a otro de menor prioridad. 2. A ningn proceso se le niega indefinidamente la ejecucin con otros procesos de la misma prioridad. La prioridad es un nmero entero en el rango de 0 a 127, siendo cero la prioridad ms baja que corresponde a los procesos de la clase Null que se analizar ms adelante. La prioridad inicial de un proceso se puede especificar mediante el constructor de Process. Por ejemplo, la definicin
Writer::Writer(char* s, int n): Process(128, 100){ cout << "Proceso " << s << "inicia su actividad\n"; ... cout << "Proceso " << s << "termina su actividad\n"; }

establece que todos los procesos de la clase Writer tienen una prioridad inicial de 100. Por otra parte, durante la ejecucin del proceso se puede 8

fijar una nueva prioridad mediante la funcin setprio() y obtener la prioridad actual mediante getprio(). 1.6. Declaracin de procesos Un proceso es una instancia de alguna clase derivada de Process. En un programa pueden coexistir diferentes instancias de la misma clase derivada. Siempre que no exista confusin, les llamaremos simplemente procesos a dichas instancias. Por ejemplo, los procesos a, b, y c, son instancias de Writer:
main(void){ Writer a("Escritor A:", 4), b("Escritor B:", 2); Writer c("Escritor C:",3); Null os; }

Cuando se declara una variable de cierta clase de procesos, se crea automticamente un proceso aunque esto no significa que su actividad comienze de inmediato. El inicio de la actividad concurrente tiene lugar con la declaracin de un proceso de la clase Null; sin ella no tendr lugar tal actividad. Se considera que todo proceso declarado antes de la declaracin de una instancia de Null, es suceptible de ponerse en ejecucin. En adelante, si algn proceso A en ejecucin declara a otro proceso B, entonces B est listo para iniciar su actividad en cualquier momento. Debemos aclarar que todo proceso, como la variable a de la clase Writer, posee una seccin que proviene de la clase base Process. A sta seccin se le llama descriptor del proceso porque contiene informacin tan importante como su prioridad y la direccin de su pila, por mencionar algunas. 1.7. Construccin y destruccin de procesos Como se vi en el apartado anterior, un proceso se crea cuando se declara una variable mediante una invocacin implcita a su constructor (creacin esttica), o durante la ejecucin de un programa mediante una invocacin expltica a su constructor, usando el operador new (creacin dinmica). Un proceso se destruye cuando el control alcanza el final del bloque que lo define o por la aplicacin del operador delete. Por ejemplo, el programa 9

anterior puede reescribirse de la siguiente forma usando los operadores new y delete:
main(void){ Writer *a, *b, *c; a = new Writer("Escritor A:", 4); b = new Writer("Escritor B:", 2); c = new Writer("Escritor C:", 3); delete new Null(); delete c; delete b; delete a; }

Observe que an cuando un proceso se haya destruido por haber alcanzado el final del bloque que lo define, su descriptor se destruir hasta que se destruya el bloque que lo contiene o por la aplicacin de delete. 1.8. Funciones componentes de procesos La clase Writer pertenece a una categora muy grande de procesos que se distinguen por no ofrecer ningn servicio: se caracterizan por una intensa actividad que demanda servicios de otros procesos. Por otra parte, los procesos administradores de recursos ofrecen servicios que a su vez deben controlar. Los servicios que ofrece un proceso corresponden a las funciones componentes pblicas de la clase (pero no las constructoras o destructoras) y pueden ser de cualquier tipo y tener cualquier nmero y tipo de parmetros. Los servicios ofrecen un medio para establecer la comunicacin sncrona bidireccional entre dos procesos, ya que tiene lugar la transferencia de la informacin una vez lograda la sincronizacin. El solicitante de un servicio hace una llamada a la funcin pblica correspondiente del prestador del servicio. Cuando el prestador del servicio se encuentra en condiciones de aceptarlo, lo hace de inmediato. En ese momento, ocurre la transferencia de la informacin a travs de los parmetros de la funcin. El solicitante enva los datos necesarios usando el paso de parmetros por valor, y obtiene los resultados usando el paso de parmetos por referencia. Una vez otorgado el servicio, tanto el solicitante como el prestador pueden continuar de inmediato sus actividades. Sin embargo, si el prestador no se enconcontrara en condiciones de aceptar la solicitud, el solicitante debe esperar a que el prestador pueda aceptar la solicitud. Cuando 10

eventualmente el prestador del servicio la acepta, comienza la atencin a la solicitud como se ha descrito. Por ejemplo, la clase Buffer, define una cola circular usada como recipiente de mensajes. En el servicio put(int) que ofrece esta clase, el paso de parmetros se hace por valor ya que los datos son enviados al recipiente. En cambio, el servicio get(int&) usa el paso de parmetros por referencia ya que recibe los datos del recipiente.
class Buffer: Process{ ... public: Buffer(void); void put(int); void get(int&); };

Puesto que los servicios que ofrece un proceso no pueden comenzar hasta que no se hayan iniciado las variables de estado, el proceso no debe ceder el control de la ejecucin hasta despus de la iniciacin. 1.9. Apuntador al proceso en ejecucin El proceso en ejecucin se determina atravs de la variable global process, declarada en el archivo ccpp.h, cuyo tipo es apuntador a la clase Process. Esta variable es extremadamente til en la depuracin de programas y en los programas que emplen corrutinas. 1.10. El proceso nulo El proceso nulo es un proceso predefinido cuyo propsito es el de simplificar el mecanismo de inicio y fin de la actividad concurrente, as como el algoritmo de asignacin del procesador. El proceso nulo posee la menor prioridad de todos los procesos. En este trabajo, el proceso nulo representa al sistema operativo que se encuentra suspendido en tanto dura el programa. Ocasionalmente, el proceso nulo se reactiva para responder a las solicitudes formuladas por otros procesos debido a que es el administrador de recursos tan importantes como la memoria.

11

La responsabilidad del proceso nulo de satisfacer las necesidades de recursos de los procesos es una consecuencia del modelo de programacin elegido en el C++ Concurrente. La consistencia que se obtiene al manejar de esta forma los recursos, deriva en la habilidad excepcional de usar el mismo administrador de memoria que provee el sistema operativo. Lo anterior ofrece la siguientes ventajas: 1. Reduce el tamao de los programas porque no tiene que incluir el cdigo del administrador de memoria. 2. Delega la responsabilidad del correcto uso de la memoria al compilador y al sistema operativo. 3. Los programas de aplicacin pueden usar las bibliotecas de funciones provistas por el compilador. 1.11. Apuntador al proceso nulo El proceso nulo se determina mediante la variable global nullprocess que es un apuntador a la clase Null derivada de Process y declarada en el archivo ccpp.h. 1.12. Procesos y corrutinas En el lenguaje C++ Concurrente, la nica diferencia entre procesos y corrutinas radica en el modo de como se transfiere el control ya que comparten la misma representacin. Una corrutina es, al igual que un procedimiento, una unidad que agrupa y oculta, bajo el nombre de la corrutina, una serie de rdenes para realizar cierta actividad. Sin embargo, a diferencia del procedimiento, no existe una relacin jerrquica entre llamador y llamado ya que el registro de activacin de ambos tiene una duracin mayor a la duracin de la llamada. En este sentido (la duracin de su ambiente) una corrutina es ms parecida a un proceso. La diferencia radica, escencialmente, en la forma de como se trasfiere del control. La corrutina transfiere el control directamente a otra corrutina y, por esta razn, debe identificarla, por ejemplo, atravs de un apuntador. Un proceso, en cambio, cede el control y es el administrador del ncleo de concurrencia el que determina el siguiente proceso a ejecutarse. Un proceso cede el control indirectamente a algn otro atravs de la funcin suspend() del administrador de procesos. Una corrutina cede el 12

control directamente a otro proceso mediante la funcin transfer() del administrador de procesos. Para ilustrar el uso de las corrutinas en el C++ Concurrente, hagamos una nueva versin del programa de los tres escritores.
#include <stream.h> #include "ccpp.h" class Writer: Process{ int c; public: Writer(char*, int, Process*&); };

En este ejemplo, el proceso a quien se debe transferir el control se pasa como una referencia a un apuntador. Esto permite construir programas con topologa dinmica ya que el control se puede asignar al proceso indicado por el apuntador.
Writer::Writer(char* s, int n, Process*& p){ cout << "Proceso " << s << " inicia su actividad\n"; for(c = 0; c < n; c++){ cout << s << c; transfer(p); // Cede el control al proceso apuntado por p } cout << "Proceso " << s << " termina su actividad\n"; } main(void){ Writer *a, *b, *c; a = new Writer("Escritor A:", 4, b); b = new Writer("Escritor B:", 2, c); c = new Writer("Escritor C:", 5, a); delete new Null(); delete c; delete b; delete a; }

Este programa crea tres procesos que se usarn como corrutinas. Cada uno de ellos conoce la forma de referirse a otro mediante el parmetro p, activndose en forma circular mediante la operacin transfer(p). Observe que las corrutinas tienen diferente duracin siendo b la que dura menos. 13

Eventualmente, cuando b haya terminado su ejecucin, la operacin de transferencia del control que realiza a ya no tendr efecto. 1.13. Estatuto select Las regiones protegidas con guardias permiten a un proceso seleccionar una serie de acciones de entre varias conforme al estado interno del proceso. Si no es posible elegir alguna de ellas, se pospone la eleccin hasta que alguna se verifique. El estatuto select contiene los mecanismos necesarias para suspender la ejecucin del solicitante cuando fallen todas las condiciones, y para reactivarlo ms tarde a que intente de nuevo. Eventualmente, cuando se verifica alguna condicin, el control ejecuta la serie de acciones que acompaan a la condicin y abandona el estatuto. Por otra parte, cuando es posible elegir ms de una alternativa, la eleccin se hace al azar. Esta incertidumbre garantiza la capacidad de modelar el no determinismo presente en las aplicaciones reales. En el lenguaje C++ Concurrente, las regiones protegidas slo pueden emplearse en la definicin de las funciones componentes de los procesos. Cuando el estatuto select aparece en la funcin constructora, el propio proceso es el nico que puede suspenderse con este estatuto. Por otra parte, cuando aparece en los servicios, los procesos que lo soliciten son los que se pueden suspender. El estatuto select que tiene la forma:
select clausula-when

en donde clausula-when es
when(condicin) alternativa

{ when(condicin-1) alternativa-1; ... when(condicin-n) alternativa-n; }.

La semntica de las regiones protegidas se rige de acuerdo con las siguientes reglas.

14

a. Un proceso A que ejecute el estatuto select puede entrar al bloque siempre que ningn otro proceso se encuentre dentro de l. b. Cuando algn otro proceso B se encuentra dentro del bloque, el proceso A se ve obligado a posponer su entrada. c. Cuando el proceso B abandona el bloque, se le permite su entrada a A. d. Si el proceso B prueba la condicin que se encuentra en alguna de las clusulas when y sta se verifica, B puede entrar al bloque que le sigue. Si ms de una se verifica, se elige una al azar y el proceso entra al bloque correspondiente. e. Por otra parte, si no se verifica ninguna condicin, el proceso B se ve forzado a esperar suspendiendo de inmediato su actividad, permitiendo el acceso al proceso que lo desee. f. Eventualmente, la espera del proceso B termina, dando lugar a que se aplique de nuevo alguno de los casos a al f. g. Cuando existe ms de un proceso que desea entrar a la regin protegida, la eleccin del proceso se hace al azar. h. Ningn proceso A puede interrumpir la ejecucin de otro proceso B hasta que B no haya fallado al probar las condiciones, o bien, que ya haya abandonado el estatuto select. Los servicios que ofrecen los procesos se consideran como operaciones indivisibles. En otras palabras, si el proceso A define dos operaciones P() y Q(), la primera de las cuales es solicitada por el proceso B, entonces A no puede ejecutar Q() hasta que B no haya terminado de ejecutar P() o se haya bloqueado. 2. CAMBIO DE CONTEXTO En ambientes con un solo procesador, la ejecucin de los procesos se hace por el asignamiento del procesador en diferentes momentos. A esto se le llama seudoparalelismo. En ambientes con mltiples procesadores, los procesos pueden correr en un procesador propio. La mayora de las implantaciones de ncleos de concurrencia contemplan algn administrador de procesos cuyas funciones subyacen en las construcciones del lenguaje. 15

Durante la ejecucin de un programa concurrente, algn evento puede causar que el proceso pierda el asignamiento del procesador. En este caso, se dice que el proceso se encuentra suspendido. Cuando esto ocurre, el proceso debe volver a recuperar el control en el misma lugar. Un proceso suspendido debe conservar el ambiente de su procesamiento de modo que pueda reanudar su actividad sin problemas. El ambiente se caracteriza por los contenidos de los registros del procesador, los contenidos del rea de memoria propia, los descriptores de archivos y los puertos, entre otros. La implantacin del ncleo de concurrencia debe asegurar la integridad del ambiente de los procesos suspendidos. Para que esto sea posible, un proceso debe poseer una pila propia en donde se conserve su estado, y de donde pueda recuperarlo ms tarde. En el descriptor de proceso se conservan los apuntadores a la regin de memoria en donde se encuentra su pila, as como cierta informacin adicional para la administracin del proceso. El cambio de contexto consiste en las acciones necesarias para suspender el proceso activo, y para reactivar otro que est en condiciones de reanudar su ejecucin. Las acciones son: 1. 2. 3. 4. 5. Guardar el ambiente de la ejecucin del proceso activo en su pila. Guardar los apuntadores a la pila del proceso en su desciptor. Elegir otro proceso que pueda reanudar su ejecucin. Restaurar los apuntadores a la pila para el proceso elegido. Finalmente, restaurar el ambiente de la ejecucin del proceso elegido.

Como se ha dicho, el cambio de contexto se realiza cuando un proceso se ve forzado a suspender su actividad en la espera de condiciones favorables que le permitan, por ejemplo, adquirir recursos. Los mecanismos de sincronizacin y comunicacin, implcitos en las construcciones que incorpora el lenguaje C++ Concurrente, usan funciones que realizan el cambio de contexto. La primera y la ltima de las acciones las realiza el prlogo y el eplogo de la funcin que realiza el cambio de contexto. La segunda y la cuarta la realiza la funcin usando el apuntador al proceso en ejecucin process. Precisamente, el propsito de las funciones de cambio de contexto es obtener el nuevo proceso que reciba el control, guardando en process la direccin de su descriptor.

16

Por lo general, en un cambio de contexto existe ms de un proceso que puede recibir el procesador por lo que se forman en la cola predefinida por el sistema readyQueue. Para resolver los conflictos que pueden surgir, se debe seguir una poltica de asignamiento. La poltica ms comn es la de formarse de nuevo para recibir el procesador (round-robin). Como su nombre lo sugiere, un proceso que ha tenido el control puede volver a tenerlo siempre que se forme de nuevo al final de la cola de candidatos. Existen algunas variantes de este esquema basados en la prioridades. La prioridad establece una preferencia en la ejecucin de un proceso sobre otro; en este caso, se dice que el primero tienen mayor prioridad que el segundo. Cuando dos procesos tienen igual prioridad, se da preferencia al ms antiguo. 2.1. El cambio de contexto en los procesos Para ilustrar lo anterior, vamos a analizar el programa de los tres escritores que ceden el control luego de cada escritura.
// Definiciones y declaraciones importantes #include <stream.h> #include "ccpp.h" // Especificacin del proceso class Writer: Process{ int c; public: Writer(char*, int); }; // Definicin del proceso Writer::Writer(char* s, int n){ cout << "Escritor " << s << " inicia su actividad\n"; for(c = 0; c < n; c++){ cout << s << c; suspend(); // Cede el control a otro proceso } cout << "Escritor " << s << " termina su actividad\n"; } // Declaracin y ejecucin main(void){ Writer a("A:", 4), b("B:", 2), c("C:",5); Null os; }

17

Este programa declara una clase especfica de procesos escritores Writer. Cada escritor posee una variable privada c de tipo entero, y una funcin pblica Writer() que es el constuctor de la clase. El constructor posee un parmetro usado para distinguir a las diferentes instancias de Writer y otro para indicar el nmero de escrituras que realizar cada proceso. El constructor establece el comportamiento del proceso: escribe el parmetro que lo identifica as como el nmero que lleva la cuenta de las iteraciones y luego, suspende su ejecucin con una llamada a suspend(). La funcin main() crea tres procesos escritores a, b y c, y al proceso nulo os. Los tres procesos a, b y c tienen el mismo tamao de su pila y la misma prioridad. La pila del proceso nulo os es la pila original del sistema operativo, asegurando que se conserve "intacto" mientras se encuentre suspendido. La prioridad de os es la menor posible, garantizando que se reactive hasta que no haya otros procesos que puedan recibir el procesador como ocurre al final del programa. En este momento, los cuatro procesos se encuentran formados en la cola readyQueue por ser los candidatos a recibir el control. El programa comienza con la funcin main(), ejecutando secuencialmente sus estatutos. Como se dijo antes, en el lenguaje C++, cuando se declara una instancia de una clase se ejecuta en ese momento el constructor de su clase. Si se trata de una clase derivada, se debe ejecutar antes el constructor de la clase base. Puesto que la clase base de Writer es Process, se debe ejecutar primero Process::Process(), creando as el descriptor del proceso para que guarde la informacin relevante del proceso. Al terminar de ejecutarse, el control regresa a Writer::Writer(). Sin embargo, antes de comenzar a ejecutar la primera instruccin de esta funcin (el estatuto for en el ejemplo), el control se transfiere de nuevo a main() para que pueda crear otro proceso en forma similar. Este "extrao" comportamiento es el que se necesita para dar la ilusin de concurrencia. De esta forma se crean los descriptores de los tres procesos a, b y c, los cuales an no comienzan propiamente su ejecucin, encontrndose suspendidos y listos para recibir el control. Finalmente, cuando el control alcanza al proceso nulo os, al igual que los anteriores se crea su descriptor, pero a diferencia de ellos es el nico tipo de proceso que ejecuta a su constructor Null::Null(). Esta funcin es la responsable de ceder el control a alguno de los procesos previamente creados mediante la orden suspend(), lo que da inicio a la actividad concurrente. Los tres procesos escritores y el proceso nulo pueden recibir el control; sin embargo, debido a que los escritores tienen mayor prioridad, sern los escritores los nicos que se manifiesten. La eleccin del escritor a 18

ejecutarse se hace al azar debido a que tienen la misma prioridad. Por ejemplo, si b es el que gana el control, entonces entra en el ciclo, escribe su mensaje y se suspende. Entonces, el proceso b se forma en la lista de elegibles y es, en este momento, cuando se elige al siguiente. Esto da como resultado que a, c, e inclusive, b puedan obtener el control. El escritor termina su actividad cuando alcanza el final del Writer::Writer(), invocando de inmediato a una funcin que se encarga de liberar el espacio de la pila usada por el proceso y de evitar que compita ms por el procesador. Luego, cede el control a otro proceso elegible. De esta forma, el proceso se destruye como entidad activa an cuando queda su descriptor como entidad pasiva. Cuando todos los escritores han sido destruidos de esta manera, solo queda el proceso nulo como el nico candidato para adquirir el control. Cuando lo hace, el control aparece en la instruccin siguiente a suspend(), en el cdigo del constructor Null::Null(), justo donde se haba quedado al iniciar la concurrencia y luego, el control regresa a main(). Finalmente, se ejecutan los destructores de los descriptores y el programa termina. Aunque aqu solo se ha discutido la creacin esttica de procesos, la creacin dinmica es similar teniendo en cuenta que el programador debe manejar explcitamente a los constructores y destructores de procesos. La tcnica descrita en ste apartado fue desarrollada por el autor y usada en la implantacin de su trabajo de tesis de maestra, el lenguaje PL/M-86 Concurrente [8]. La tcnica sugiere un esquema general y a la vez flexible, para extender un lenguaje estructurado por bloques. 2.2. El cambio de contexto en las corrutinas En el C++ Concurrente, la nica diferencia entre proceso y corrutina es el modo de ceder el control. Otra forma de decirlo, es que los procesos pueden ceder el control al estilo de las corrutinas. De hecho, desde el punto de vista del modelo de proceso distribuido, las corrutinas son casos particulares de los procesos. La llamada transfer(newprocess) transfiere directamente el control al proceso apuntado por newprocess, quien a su vez puede ceder el control a otro directamente (como corrutina) o indirectamente (como proceso). En tanto, el proceso que originalmente cedi el control se quedar esperando. Cuando lo reciba, su ejecucin se reanudar en el estatuto que sigue a la 19

llamada de transfer(newprocess). Si newprocess transfer(newprocess) no tiene ningn efecto. 3. EJEMPLOS

ya

fu

destruido,

A continuacin se presentan algunos problemas de concurrencia y su solucin usando el lenguaje C++ Concurrente. La mayora de ellos fueron tomados directamente del artculo de Brinch Hansen [3]. Esperamos que el lector interesado los estudie y los compare con las soluciones escritas en otros lenguajes concurrentes. Los programas que aqu aparecen fueron probados en el prototipo desarrollado en el lenguaje C++ de Borland. 3.1. Semforos Un semforo consiste de un contador cntr, de una cola de procesos queue y de dos operaciones que lo accesan wait() y signal(). Cuando el contador es un nmero negativo, el proceso que emite la operacin wait() se bloquea hasta que el contador sea cero o un nmero positivo. La operacin signal() incrementa siempre al contador rehabilitando el proceso suspendido por wait().
#include "ccpp.h" // Especificacin de la clase class Semaphore: Process{ Queue queue; int cntr; public: Semaphore(int); void wait(void); void signal(void); }; Semaphore::Semaphore(int n): Process(0){ if(n >= 0) cntr = n; } void Semaphore::wait(void){ if(--cntr < 0) suspend(queue, readyQueue); } void Semaphore::signal(void){ if(cntr++ < 0) readyQueue.add(queue.sub()); }

20

La llamada suspend(queue, readyQueue) que aparece en wait() establece que el proceso en ejecucin ser formado en queue y que el siguiente ser obtenido de la cola de candidatos readyQueue. Por otra parte, en la llamada readyQueue.add (queue.sub()) que aparece en signal(), extrae al proceso que primero haya llegado a queue, y despus lo hace elegible para recibir el control insertndolo en readyQueue. Se puede formular una versin equivalente usando regiones protegidas como lo sugiere Brinch Hansen. Recuerde que las regiones protegidas slo pueden usarse en la definicin de las funciones componentes de un proceso de modo que en esta versin, la clase Semaphore debe especificarse como un proceso, aunque jams volver a competir por el procesador despus de realizar la iniciacin. De hecho, el proceso se destruye lo cul no tiene ningn efecto sobre los procesos que usan las operaciones, debido a que la actividad del proceso servidor no incluye la administracin del semforo.
#include "ccpp.h" class Semaphore: Process{ int cntr; public: Semaphore(int init); void wait(void); void signal(void); }; Semaphore::Semaphore(int init): Process(0){ if(init >= 0) cntr = init; } void Semaphore::wait(void){ select when(cntr > 0) cntr--; } void Semaphore::signal(void){ cntr++; }

Los procesos que usen estas operaciones deben hacerlo as:


{ Semaphore s(1); s.wait(); ... // Regin crtica s.signal(); }

21

lo que ilustra una solucin al problema de semforos. El proceso semforo es un caso servicios: su actividad se reduce a establecer variables. En adelante, solamente atiende las de otros procesos. 3.2. Recipiente de mensajes

la exclusin mutua usando tpico de un prestador de los valores iniciales de las solicitudes wait() y signal()

Un proceso recipiente de mensajes almacena una secuencia de nmeros enteros transmitidos entre procesos por medio de las operaciones send() y receive().
#include "ccpp.h" class Buffer: Process{ int sz, cn, hd, tl, *st; void get(int&); void put(int); public: Buffer(int); void send(int); void receive(int&); }; Buffer::Buffer(int size): Process(0){ cn = hd = tl = 0; st = new int[sz = size]; } void Buffer::put(int& c){ st[tl] = c; tl = (tl + 1) % N; cn++; } void Buffer::send(int c){ select when(cn < sz) put(c); } void Buffer::get(int c){ c = st[hd]; hd = (hd + 1) % N; cn--; } void Buffer::receive(int& c){ select when(cn > 0) get(c); }

Las operaciones sobre el recipiente se pueden hacer as:


{ Buffer b(100); int x = 7; b.send(x); // Enva el contenido de x

22

... b.receive(x); // Recibe un mensaje en x }

Al igual que los semforos, despus de iniciar sus variables el Buffer no requiere ms el procesador, porque solamente define las estructuras de datos y las operaciones significativas sobre ellas. 3.3. Administrador de tareas Existe una gran variedad de problemas de administracin de recursos que se pueden resolver mediante regiones protegidas. Un recurso es por naturaleza "un cuello de botella" cuya rendimiento aceptable depende de que sean pocos los procesos que lo soliciten por mucho tiempo, o bien, que sean muchos procesos pero que lo usen por poco tiempo. Un administrador de recursos que sigue la poltica el ms corto primero, (shortest job next) asigna un recurso a un nmero limitado de N procesos de usuarios. La solicitud del recurso request() proporciona la identidad del proceso solicitante y el tiempo de servicio que requiere. La liberacin del recurso release() indica que el recurso se encuentra de nuevo disponible. El administrador usa las siguientes variables: una lista de identificadores representada por un conjunto de bits queue, un arreglo de enteros para guardar el tiempo que cada proceso requiere rank, el ndice del usuario actual (si lo hay) user, y el ndice del usuario siguiente next. La especificacin de la clase Set y de Scheduler se dan a continuacin:
#include #include #define #define #define <stream.h> "ccpp.h" N 16 NIL -1 MININT 32767

class Set{ int bits, size; public: Set(void){bits = size = 0;} void include(int){if(0 <= e && e < N){bits |= 1<<e; size++;}} void exclude(int){if(0 <= e && e < N){bits &= ~(1<<e); size--;}} int contain(int){if(0 <= e && e < N) return bits & (1<<e); int card(void){return size;}

23

}; class Scheduler: Process{ Set queue; int rank[N]; int user, next; public: Scheduler(void); void request(int, int); void release(void); };

Una vez iniciadas las variables, el administrador espera hasta que una de dos situaciones tiene lugar: 1. Un proceso entra o sale de la lista queue: el administrador examinar la lista y seleccionar al siguiente usuario si es que lo hay. Sin embargo, esto no significa que el recurso se le asigne. 2. El recurso no est siendo usado y el siguiente usuario ya haba sido seleccionado: el administrador asigna entonces el recurso al proceso seleccionado y lo remueve de la lista de espera.
Scheduler::Scheduler(void){ user = next = NIL; for(;;) select{ when(queue.card() > 0 && next == NIL){ int i, k, min = MININT; for(i=0, k=0; i < N && k < queue.card(); i++) if(queue.contain(i) && rank[i] <= min){ next = i; min = rank[i]; k++; } } when(user == NIL && next != NIL){ user = next; queue.exclude(user); } } } void Scheduler::release(void){ user = NIL; } void Scheduler::request(int who, int size){

24

queue.include(who); rank[who] = size; select when(user == who) next = NIL; }

Los procesos de usuario se identifican mediante un nmero nico entre 0 y N-1. Su comportamiento consiste en solicitar el recurso administrado por sjn por un tiempo arbitrario, y ms tarde, en liberarlo.
class User: Process{ public: User(Scheduler&,int); }; User::User(Scheduler& sjn, int id){ int r; for(int i = 0; i < 100; i++){ r = random(5)+1; cout << id << "solicita el recurso por " << r << "secs.\n"; sjn.request(id, r); cout << id << "usando el recurso por " << r << "secs.\n"; delay(r); sjn.release(); cout << id << "us el recurso\n"; } }

El programa principal consiste de las declaraciones de los procesos incluida la del proceso nulo.
main(){ Scheduler s; User a(s,0), b(s,1), c(s,2), d(s,3); Null os; }

La administracin de cada proceso se maneja en dos niveles de abstraccin: al nivel de las operaciones request() y release(), y al nivel de las regiones protegidas con select. La evaluacin peridica de la condicin de sincronizacin puede ser una serie sobrecarga para el rendimiento del sistema. Sin embargo, es aceptable cuando se hace la distribucin del procesamiento y un solo procesador est dedicado a ello. 25

3.4. Lectores y escritores Dos clases de procesos, llamados lectores y escritores, comparten un recurso. Los lectores pueden usar el recurso simultaneamente, pero cada escritor debe tener acceso exclusivo a l. Los lectores y escritores se comportan como sigue: Una variable s define el estado actual del recurso como uno de los siguientes: s= s= s= s= ... 0 1 2 3 1 0 1 2 escritor usa el recurso procesos usan el recurso lector usa el recurso lectores usan el recurso

El anterior esquema da lugar a la siguiente solucin [12]. El proceso Resource regula el paso de los lectores Reader y de los escritores Writer mediante las operaciones startread, endread, startwrite, endwrite, respectivamente.
#include <stream.h> #include "ccpp.h" #define TIME 500 class Resource: Process{ int s; public: Resource(void); void startread(void); void endread(void); void startwrite(void); void endwrite(void); }; class Reader: Process{ public: Reader(int, Resource&); }; class Writer: Process{ public: Writer(int, Resource&); };

26

Las definiciones de los procesos y sus operaciones son:


Resource::Resource(void): Process(0){ s = 1; } void Resource::startread(void){ select when(s >= 1) s++; } void Resource::endread(void){ if(s > 1) s--; } void Resource::startwrite(void){ select when(s == 1) s = 0; } void Resource::endwrite(void){ if(s == 0) s = 1; } Reader::Reader(char* id, Resource& book){ for(int i = 0; i < 10; i++){ cout << "Lector %s solicita recurso " << id; book.startread(); cout << "Lector %s usando el recurso..." << id; delay(TIME); book.endread(); cout << "Lector %s libera el recurso " << id; } } Writer::Writer(int id, Resource& book){ for(int i = 0; i < 10; i++){ cout << "Escritor %s solicita recurso " << id; book.startwrite(); cout << "Escritor %s usando el recurso..." << id; delay(TIME); book.endwrite(); cout << "Escritor %s libera el recurso " << id; } } main(){ Resource book; for(int i = 0; i < 10; i++){ new Reader(i, book); new Writer(i, book);

27

} new Null(); }

3.5. Los filsofos Cinco filsofos alternan sus actividades de pensar y comer. Cuando un filsofo tiene hambre, se dirige a una mesa redonda y toma los dos cubiertos ms cercanos al plato y comienza a comer. Sin embargo, solamente hay cinco cubiertos en la mesa, asi que un filsofo slo puede comer cuando ninguno de sus vecinos inmediatos lo est haciendo. Cuano un filsofo termina de comer, pone los dos cubiertos de nuevo en la mesa y la abandona. La especificacin de las clases Table y Philosopher es como sigue:
#include <stream.h> #include "ccpp.h" class Table: Process{ Set eating; public: Table(void); void join(int); void leave(int); }; class Philosopher: Process{ public: Philosopher(int,Table&); };

El proceso Table usa la clase conjunto Set definida en el ejemplo 3. El conjunto eating registra a los filsofos que se encuentran comiendo en la mesa. Por esta razn, la operacin join() establece que es necesario esperar hasta que los dos filsofos que lo rodean hayan dejado de comer para que el pueda empezar. Cuando el filsofo abandona la mesa, se debe excluir del conjunto eating.
Table::Table(void): Process(0){ // Actividad totalmente nula } void Table::join(int i){ select when(!eating.contain((i+4) % 5) && !eating.contain((i+1) % 5))

28

eating.include(i); } void Table::leave(int i){ eating.exclude(i); }

El filsofo i aplica las operaciones table.join(i) y table.leave(), en ese orden, para sincronizarse con los otros filsofos.
Philosopher::Philosopher(int id, for(i = 0; i < 10; i++){ cout << "Filsofo %d table.join(id); cout << "Filsofo %d delay(TIME); table.leave(id); cout << "Filsofo %d delay(TIME); } } Table& table){ hambriento" << id; comiendo...." << id;

pensando" << id;

Los filsofos se pueden crear como un arreglo de procesos, o ms exactamente, como un arreglo de apuntadores a procesos Philosopher. Observe, que este programa no necesita realmente conocer las direcciones de los filsofos por lo que podemos precindir de arreglo.
main(){ Table table; for(int i = 0; i < 5; i++) new Philosopher(i, table); new Null(); }

La solucin que se presenta no previene que dos filsofos coman alternativamente, impidiendo que coma el filsofo que se encuentra en medio. 3.6. Ordenamiento de un arreglo Un arreglo de procesos puede ordenar un arreglo en un tiempo proporcional al tamao de un arreglo de datos. Los datos se envan al primer proceso del arreglo quien conserva al menor de todos ellos y pasa 29

el resto al segundo. Ms tarde, el segundo proceso conserva el menor de los datos que ha recibido y enva el resto al siguiente, y as sucesivamente. Cuando se agotan los datos del arreglo, cada proceso mantendr el dato que le corresponde de acuerdo con su orden natural: el primer proceso conserva el menor de los datos, el segundo conserva el dato que le sigue, etc. Para recuperar los datos ya ordenados, solicitamos los datos al primer proceso del arreglo quien responder de inmediato con el dato que guarda. Ms tarde, el segundo proceso enva el dato que guarda al primer proceso. Este procedimiento hace que los datos se muevan hacia los predecesores del arreglo hasta que finalmente se agotan los datos.
#include <stream.h> #include "ccpp.h" class Sort: Process{ int here[2], length, rest, temp; public: Sort(int); void put(int); void get(int&); }; Sort* sort[N];

El proceso Sort usa la variable here para guardar los datos que recibe, length para indicar el nmero de ellos, rest es el nmero de datos pasados a sus sucesores y temp contiene el dato que ser pasado a su sucesor. Las operaciones put() y get() envan y reciben los datos hacia y del proceso, respectivamente. La variable global sort es el arreglo de N apuntadores a los procesos que realizarn el ordenamiento.
class User: Process{ public: User(void); };

El proceso de usuario User enva y recibe los datos al primer proceso del arreglo con las llamadas sort[0]->put() y sort[0]->get(), respectivamente . User define el arreglo a de M enteros que sern ordenados. El nmero de elementos del arreglo M debe ser menor al nmero de procesos N.
User::User(void){ int i, a[M]={6,2,7,9,3,5,8,1,0,4}; cout << "\n\nArreglo desordenado: "; for(i = 0; i < M; i++)

30

cout << a[i]; for(i = 0; i < M; i++) sort[0]->put(a[i]); for(i = 0; i < M; i++) sort[0]->get(a[i]); cout << "\nArreglo ordenado: "; for(i = 0; i < M; i++) cout << a[i]; };

Un proceso del arreglo se encuentra en equilibrio cuando guarda un dato nada ms. Dicho equilibrio se ve alterado por el proceso predecesor cuando le enva un dato o cuando se lo solicita. Para recuperar el equilibrio, se debe tener en cuenta dos situaciones: 1. Si el proceso posee dos datos, mantendr el menor de los dos y pasar el mayor a su sucesor. 2. Si el proceso no posee ningn dato pero su sucesor s, entonces se lo solicita a l. El ndice succ determina el proceso siguiente:
Sort::Sort(int succ){ length = rest = 0; for(;;) select{ when(length == 2){ if(here[0] <= here[1]) temp = here[1]; else { temp = here[0]; here[0] = here[1]; } length--; sort[succ]->put(temp); rest++; } when(length == 0 && rest > 0){ sort[succ]->get(temp); rest--; here[0] = temp; length++; } } } void Sort::put(int c){

31

select when(length < 2){ here[length] = c; length++; } } void Sort::get(int& c){ select when(length == 1){ length--; c = here[length]; } }

Los procesos se crean mediante el operador new guardando sus direcciones en sort.
main(){ for(int i = 0; i < N; i++) sort[i] = new Sort(i+1); new User(); new Null(); }

3.8. Construccin dinmica de procesos El lenguaje C++ concurrente permite construir procesos a tiempo de ejecucin. A diferencia de los ejemplos anteriores, es posible que un proceso construya a otro (inclusive de otra clase) mediante las funciones new y delete. Para demostrarlo, presentamos el ejemplo clsico del programa factorial que crea nuevos procesos en forma recursiva.
class Factorial: Process{ Factorial* factorial; Semaphore* next; public: Factorial(long n, long& r, Semaphore* prev); }; Factorial::Factorial(long n, long& r, Semaphore* prev){ if(n == 0){ r = 1; prev->signal(); } else { next = new Semaphore(0);

32

factorial = new Factorial(n - 1, r, next); next->wait(); r = n * r; prev->signal(); delete factorial; delete next; } } class Caller{ Factorial* factorial; Semaphore* next; public: Caller(long n, long& r); }; Caller::Caller(long n, long& r){ next = new Semaphore(0); factorial = new Factorial(n, r, next); next->wait(); delete factorial; delete next; } main(){ long n = 10, r; Caller factorial(n, r); Null os; cout << "Factorial(" << n << ")=" << r; }

El proceso factorial de n, crea a un semforo next, y a otro proceso factorial para n-1. El parmetro n se enva del factorial de n al factorial de n-1. El parmetro r lo recibe el factorial de n del factorial de n-1. El semforo next, sincroniza a los procesos, de modo que el factorial de n espera a que el factorial de n-1 calcule el resultado. Cuando esto ocurre, el factorial de n reanuda su ejecucin. El proceso Caller slo se usa para iniciar las llamadas al Factorial. Los dos ejemplos anteriores demuestran algunas de las capacidades del C++ Concurrente: (1) la creacin y destruccin dinmica de procesos, (2), la topologa dinmica de procesos, y, (3) la comunicacin sncrona bidireccional. 3.9. Trayectorias (Path expressions) 33

Las trayectorias definen secuencias de operaciones similares a las expresiones regulares [Campbell, R.H. & Habermann, A.N. 1974]. Las trayectorias se pueden representar por procesos que definen las operaciones y de variables de estado que sincronizan su actividad estableciendo un orden en la ejecucin de las operaciones. Secuencia. Supongamos que la operacin P debe ejecutarse antes que la operacin Q, como se muestra en la figura,
-> P -> Q ->

en el lenguaje C++ Concurrente se puede correspondiente, introduciendo estados (a, b y c)


-> [s == a] P -> [s == b] Q ->

obtener

un

esquema

[s == c]

y usando regiones crticas


Path::P(){select when(s == a){...; s = b;}} Path::Q(){select when(s == b){...; s = c;}}

Alternativas. El diagrama que sigue indica que tanto la operacin P como la operacin Q pueden ejecutar en un momento dado.
|-> ->| |-> [s == a] P [s == a] Q ->| |-> [s == b] ->|

que corresponde a
Path::P(){select when(s == a){...; s = b;}} Path::Q(){select when(s == a){...; s = b;}}

Repeticiones. El siguiente diagrama muestra la repeticin de la operacin P cero, una o ms veces.


|<->|-> P [s == a] -> <-| ->| -> [ s == a]

que corresponde a
Path::P(){select when(s == a){...}}

34

Las trayectorias pueden usarse para ambientes de programacin visual que se basen en algn modelo de concurrencia para imponer un orden en su evaluacin. El administrador de recursos presentado en el ejemplo 3, se puede representar por medio de trayectorias formadas por la secuencia de operaciones request() ... release() de cero, una o ms ocasiones. El problema de los lectores y escritores ilustra el uso de variables de estado que permitan que algunas operaciones tengan lugar simultaneamente mientras otras son excluidas. Por ejemplo, varios lectores pueden operar simultaneamente excluyendo a los escritores, o bien, que un escritor puede excluir a los lectores.

35

4. DIRECCIONES FUTURAS Este trabajo se puede extender en varias direcciones, sobre algunas de las cuales, ya se ha comenzado a trabajar. 4.1. Desarrollar un preprocesador externo para mejorar el rendimiento de los programas diseados con la implantacin actual. 4.2. Incorporar algunas construcciones que le den mayor poder y expresividad al lenguaje. En particular, dichas construcciones establecen una forma de resolver la asimetra que se presenta cuando se usan las regiones protegidas, en donde, el proceso dueo de la regin es el nico que impone las condiciones necesarias para satisfacer las solicitudes de servicio. 4.3. Incluir objetos remotos. Los objetos remotos son una abstraccin que permite unificar diferentes conceptos de la programacin: llamada a un procedimiento local, llamada a un procedimiento remoto. Este enfoque permite tratar en forma coherente, a varios conceptos: las comunicaciones, las bases de datos heterogeneas, las bases de datos distribuidas, etc. Estas direcciones son temas importantes de investigacin actual y el plateamiento de la programacin concurrente orientada a objetos puede usarse como marco de referenica para el desarrollo de sistemas simples y eficientes. 5. CONCLUSIONES El lenguaje C++ Concurrente se basa en el modelo de proceso distribuido de Brinch Hansen. La razn de haber elegido este modelo es la generalidad con la que trata diferentes conceptos de la programacin concurrente y la orientada a objetos, as como la facilidad para implementarse. El concepto de proceso distribuido incluye un considerablemente amplio conjunto de conceptos como casos especiales lo que demuestra su enorme poder expresivo. El lenguaje C++ Concurrente encuentra aplicaciones en diferentes reas: lenguajes de programacin, programacin concurrente, programacin en tiempo real, programacin orientada a objetos, anlisis y diseo asistido por computadora, computacin distribuida, control automtico, sistemas 36

operativos, protocolos de simulacin, entre otras. 6. REFERENCIAS

comunicacin,

redes

de

computadoras

1. Andler, S. [1979] "Predicate path expressions". Conference record of the sixth annual ACM Symposium on principles of programming languages. 2. Brinch Hansen, P. [1973] "Operating system principles". Prentice Hall. 3. Brinch Hansen, P. [1978] "Distributed process: a concurrent programming concept". CACM, vol.21, no. 11. 4. Dijkstra, E. W. [1968] "Guarded commands, nondeterminacy and formal derivation of programs". CACM. vol.18, no. 8. 5. Ichibiah, [1983] "Reference manual for the Ada programming language". United States Departament of Defense. 6. Gehani, N. and Roome, W. [1989] "The Concurrent C". Silicon Press. 7. Hoare, C. A. R. [1978] "Communicating Sequential Process". CACM, vol.17, no.10 (October), pp.549-557. 8. Olmedo, O. [1987] "PL/M-86 Concurrente". Tesis de maestra. Seccin de Computacin. Departamento de Ingeniera Elctrica. CINVESTAV IPN. 9. Peterson, J. & Silverschatz, A. [1981] "Operating systems concepts". Prentice Hall. 10. Stroustrup, B. [1986] "The C++ Programming Language". Adisson Wesley. 11. Dahl, O., Dijkstra, E., & Hoare, C.A.R., "Structured Programming". 12. Brinch Hansen, P. & Staunstrup, J. [1977] "Specification and implementation of mutual exclusion". Comptr. Sci. Dept., U. of Southern California, Los Angeles. 13. Hansen,T.L., [1990] "The C++ answer book". Addison Wesley.

37

Vous aimerez peut-être aussi