Está en la página 1de 12

Distribucin de Recursos: Ejemplo del Algoritmo de los Filsofos Comensales

Alberto Garcia-Robledo
Laboratorio de Tecnologas de Informacin, CINVESTAV-Tamaulipas, Carretera Nacional Cd. Victoria-Monterrey Km 6, Cd. Victoria Tamaulipas, Mxico, CP. 87276, Tel. (52 834) 316 6600 algarcia@tamps.cinvestav.mx

1.

Introduccin

El presente documento muestra la ejecucin de un sistema distribuido mnimo que ejemplifica el funcionamiento bsico del algoritmo de los filsofos comensales. Tanto los programas de los componentes del sistema como del algoritmo fueron tomados de [1]. El cdigo fuente puede ser descargado de [2], aunque se incluye distribuido junto con este documento.

2. Ejemplo de los filsofos comensales


Cualquier implementacin de algoritmo de exclusin mutua presentado en el captulo 8 de [1] puede ser probado utilizando el programa del listado 1.1. La lnea 8 crea un objeto Linker. La clase Linker, mostrada en el listado 1.2, permite enlazar un conjunto de procesos. A continuacin se explicar a groso modo el funcionamiento de la clase Linker. Para iniciar n procesos en un sistema distribuido y establecer conexiones entre ellos de modo que cada proceso pueda enviar y recibir mensajes a cualquier otro, es necesario un servicio de nombres de modo que cada proceso no tenga que saber el nombre y el puerto del proceso al que desea conectarse, sino slo saber el identificador, como se explica ms adelante. Cada proceso lee la topologa del overlay a partir de archivos especiales. Luego, se crea un socket servidor que escucha por peticiones. Inmediatamente, se conecta al servidor de nombres para determinar los nombres y puertos de los procesos con quien se desea conectar. Una vez establecidas las conexiones, un objeto Linker provee la infraestructura para realizar operaciones de envo y recepcin de mensajes. Detalles sobre la implementacin de la clase Linker pueden ser encontrados en el captulo 6 titulado Distributed Programming en [1]. Regresando a la explicacin del listado 1.1, despus de instanciar una implementacin de algn algoritmo de exclusin mutua en las lneas 10 a 17, iniciamos un hilo que escucha los mensajes de otros procesos en las lneas 18 a 20. El bucle while de la lnea 21 itera indefinidamente. En cada iteracin el hilo que representa el proceso duerme por 2 segundos para luego solicitar acceso a la seccin crtica utilizando la instancia del algoritmo seleccionado. Como se explic en la exposicin, este mtodo bloquea el hilo hasta que, segn el algoritmo elegido, es despertado dado a que se le ha concedido el acceso a la seccin crtica. Despus, se simula un

procesamiento que tarda dos segundos durmiendo el hilo durante este tiempo. Finalmente, se libera el acceso a la seccin crtica a travs de la instancia del algoritmo seleccionado antes de iniciar otra iteracin. En resumen, el programa 1.1 instancia un algoritmo de exclusin mutua determinado, lanza un hilo que representa el proceso actual y entra a un bucle infinito. En cada iteracin el hilo solicita acceso a la seccin crtica utilizando la instancia del algoritmo elegido, se bloquea hasta obtener el acceso y simula un procesamiento que dura dos segundos antes de liberar la seccin crtica y empezar una nueva iteracin. Listing 1.1. LockTester.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

21 22 23 24 25 26 27 28 29 30 31 32 33 34 35

p u b l i c c l a s s Lock Tester { p u b l i c s t a t i c void main ( S t r i n g [ ] a r g s ) throws E xc e pti o n { Li n ke r comm = n u l l ; tr y { S t r i n g baseName = a r g s [ 0 ] ; i n t myId = I n t e g e r . p a r s e I n t ( a r g s [ 1 ] ) ; int numProc = I n t e g e r . p a r s e I n t ( a r g s [ 2 ] ) ; comm = new Li n ke r ( baseName , myId , numProc ) ; Lock l o c k = n u l l ; i f ( a r g s [ 3 ] . e q u a l s ( Lamport ) ) l o c k = new LamportMutex (comm) ; i f ( a r g s [ 3 ] . e q u a l s ( Ricart Agrawala ) ) l o c k = new RAMutex(comm) ; i f ( a r g s [ 3 ] . e q u a l s ( Di ni ng Phil ) ) l o c k = new DinMutex (comm) ; i f ( a r g s [ 3 ] . e q u a l s ( CircToken ) ) l o c k = new CircToken (comm, 0 ) ; f o r ( i n t i = 0 ; i < numProc ; i ++) i f ( i != myId ) ( new L i s te ne r Thre ad ( i , ( MsgHandler ) l o c k ) ) . start () ; while ( t r u e ) { System . out . p r i n t l n ( myId + i s not i n CS ) ; U t i l . mySleep ( 2000 ) ; l o c k . re q ue s t C S ( ) ; U t i l . mySleep ( 2000 ) ; System . out . p r i n t l n ( myId + i s i n CS ) ; lock . releaseCS () ; } } c atc h ( I n t e r r u p t e d E x c e p t i o n e ) { i f (comm != n u l l ) comm . c l o s e ( ) ; } catch ( Exception e ) { System . out . p r i n t l n ( e ) ; e . pri nt S tac k Trace ( ) ;

36 37 38

} }

Listing 1.2. Linker.java


1 2 3 4 5 6 7 8 9 10

11 12 13 14 15 16 17

18 19 20 21 22 23 24 25 26 27

28 29 30 31 32 33 34 35 36 37 38 39 40 41

import java . u t i l . ; import java . i o . ; p u b l i c c l a s s Linker { Print Writer [ ] dataOut ; Buf f ered Re ader [ ] data In ; Buf f ered Re ader dIn ; int myId , N; Connector c o nne c to r ; p u b l i c I n t L i n k e d L i s t n e i g h b o r s = new I n t L i n k e d L i s t ( ) ; p u b l i c Linker ( S t r i n g basename , int id , int numProc ) throws Exception { myId = i d ; N = numProc ; data In = new Buf f ered Reade r [ numProc ] ; dataOut = new Print Write r [ numProc ] ; Topology . read Ne i ghbo rs ( myId , N, n e i g h b o r s ) ; c o nne c to r = new Connector ( ) ; c o nne c to r . Connect ( basename , myId , numProc , data In , dataOut ) ; } p u b l i c void sendMsg ( int dest Id , S t r i n g tag , S t r i n g msg ) { dataOut [ d e s t I d ] . p r i n t l n ( myId + + d e s t I d + + tag + + msg + # ) ; dataOut [ d e s t I d ] . f l u s h ( ) ; } p u b l i c void sendMsg ( int dest Id , S t r i n g tag ) { sendMsg ( dest Id , tag , 0 ) ; } p u b l i c void m u l t i c a s t ( I n t L i n k e d L i s t d e s t I d s , S t r i n g tag , S t r i n g msg ) { for ( int i =0; i < d e s t I d s . s i z e ( ) ; i ++) { sendMsg ( d e s t I d s . get Entry ( i ) , tag , msg ) ; } } p u b l i c Msg re cei ve Msg ( int from Id ) throws IOException { S t r i n g g e t l i n e = data In [ from Id ] . read Line ( ) ; U t i l . p r i n t l n ( r e c e i v e d message + g e t l i n e ) ; S t r i n g T o k e n i z e r s t = new S t r i n g T o k e n i z e r ( g e t l i n e ) ; int s r c I d = I n t e g e r . p a r s e I n t ( s t . nextToken ( ) ) ; i n t d e s t I d = I n t e g e r . p a r s e I n t ( s t . nextToken ( ) ) ; S t r i n g tag = s t . nextToken ( ) ; S t r i n g msg = s t . nextToken ( # ) ; return new Msg( s r c I d , dest Id , tag , msg ) ; }

42 43 44 45

p u b l i c int getMyId ( ) { return myId ; } p u b l i c int getNumProc ( ) { return N; } p u b l i c void c l o s e ( ) { connector . c l o s e S o c k e t s ( ) ; }

El programa 1.1 es ejecutado de manera concurrente tantas veces como nmero de proceso queremos que tenga nuestro sistema distribuido. Al ejecutar cada instancia del programa, se le es asignado un identificador numrico y cero-basado dentro del sistema distribuido. Para cada proceso existe un archivo que contiene los identificadores de ese proceso. El nombre del archivo sigue el formato topology#, en donde # es reemplazado por el identificador del proceso al cual el archivo pertenece. Gracias a estos archivos, es posible determinar la topologa que define la manera en la que los procesos se comunican unos con otros. Esto es particularmente til para nuestro ejemplo, en el que los filsofos se sientan en una mesa redonda, por lo cual los procesos deben estar organizados en un anillo, como se muestra en la figura 1.

Figura 1. Topologa del sistema distribuido que representa el a los filsofos comensales

El listado 1.4 muestra un script que muestra la manera en que se ejecutan cinco procesos de manera concurrente sincronizados entre s mediante el algo- ritmo de los filsofos comensales. Como puede observarse, el primer paso es la ejecucin en segundo plano de un programa NameServer, el cual se muestra en el listado 1.3. NameServer es un servidor de nombres sencillo, que permite a cada proceso saber sobre los dems. El servidor de nombres mantiene una tabla con los campos name, hostName, portNumber a travs de un objeto NameTable que proporciona un

mapeo entre el nombre de un proceso al host y puerto en el que se ejecuta. Las operaciones bsicas que proporciona son insert (agregar proceso a la tabla) y search (buscar proceso en la tabla). Detalles sobre la implementacin del servidor de nombres pueden ser encontrados en el captulo en [1]. Listing 1.3. NameServer.java
1 2 3 4 5 6 7 8 9 10 11 12

import java . net . ; import java . i o . ; import java . u t i l . ; p u b l i c c l a s s NameServer { NameTable t a b l e ; p u b l i c NameServer ( ) { t a b l e = new NameTable ( ) ; } void h a n d l e c l i e n t ( S oc ke t t h e C l i e n t ) { tr y { Buf f ered Reader din = new Buf f ered Reader ( new Input Stream Reader ( t h e C l i e n t . get Input Stre am ( ) ) ); Pri nt Wri te r pout = new Pri nt W ri te r ( t h e C l i e n t . getOutputStream ( ) ) ; S t r i n g g e t l i n e = din . read Line ( ) ; S t r i n g T o k e n i z e r s t = new S t r i n g T o k e n i z e r ( g e t l i n e ) ; S t r i n g tag = s t . nextToken ( ) ; i f ( tag . e q u a l s ( s e a r c h ) ) { int i nde x = t a b l e . s e a r c h ( s t . nextToken ( ) ) ; i f ( index == 1) // not found pout . p r i n t l n ( 1 + + n u l l h o s t ) ; else pout . p r i n t l n ( t a b l e . get Po rt ( inde x ) + + t a b l e . getHostName ( i nde x ) ) ; } else i f ( tag . e q u a l s ( i n s e r t ) ) { S t r i n g name = s t . nextToken ( ) ; S t r i n g hostName = s t . nextToken ( ) ; int po rt = I n t e g e r . p a r s e I n t ( s t . nextToken ( ) ) ; int ret Val ue = t a b l e . i n s e r t ( name , hostName , po rt ) ; pout . p r i n t l n ( ret V al ue ) ; } pout . f l u s h ( ) ; } catch ( IOException e ) { System . e r r . p r i n t l n ( e ) ; } } p u b l i c s t a t i c void main ( S t r i n g [ ] a r g s ) {

13

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

29 30 31 32 33 34 35 36

37 38 39 40

41 42 43 44 45 46 47 48 49 50

NameServer ns = new NameServer ( ) ; System . out . p r i n t l n ( NameServer s t a r t e d : ) ; tr y { S e r v e r S o c k e t l i s t e n e r = new S e r v e r S o c k e t ( Symbols . S e r v e r Po rt ) ; while ( t r u e ) { Soc ke t a C l i e n t = l i s t e n e r . a c c e p t ( ) ; ns . h a n d l e c l i e n t ( a C l i e n t ) ; aClient . close () ; } } catch ( IOException e ) { System . e r r . p r i n t l n ( S e rv e r a bo rte d : + e ) ; } } }

A cada proceso se le indica mediante los argumentos del programa el identificador del proceso, el nmero de procesos total (5 en nuestro caso) y el nombre del algoritmo de exclusin mutua que deseamos que utilice. Las claves de los posibles algoritmos son: Lamport RicartAgrawala DiningPhil (el que estamos probando) CircToken Listing 1.4. init-DiningPhil.bat Start java NameServer Start "Filosofo 0" java LockTester "Filosofos" 0 5 DiningPhil Start "Filosofo 1" java LockTester "Filosofos" 1 5 DiningPhil Start "Filosofo 2" java LockTester "Filosofos" 2 5 DiningPhil Start "Filosofo 3" java LockTester "Filosofos" 3 5 DiningPhil Start "Filosofo 4" java LockTester "Filosofos" 4 5 DiningPhil Cada proceso se ejecuta en una termina cmd diferente, de modo que podamos visualizar el estado de cada proceso de manera individual. Por ejemplo, la figura 2 muestra la salida que expone el proceso 0 (filosofo 0) en un momento determinado. La figura siguiente se muestra la ejecucin de los 5 procesos filsofos corriendo de manera concurrente y sincronizada por el algoritmo de los filsofos comensales.

Figura 2. Un filsofo comiendo y pensando

En otras palabras, es posible ver a los cinco filsofos sentados en la mesa comiendo y pensando de manera sincronizada (aunque solo sea de manera textual). En la consola de cada filsofo es posible observar de manera secuencial cada uno de los mensajes de sincronizacin que llegan y salen de cada uno de ellos. Cuando un filsofo tiene hambre, pide sus cubiertos solo a aquellos filsofos que los comparten con el (sus dos vecinos en nuestro caso), por lo que les enva el mensaje request. El filsofo se bloque en espera de la respuesta de ambos filsofos, es decir, hasta que recibe un mensaje Fork de cada uno de sus vecinos. Es entonces cuando entra a la seccin crtica durante dos segundos antes de soltar los cubiertos. Mientras come, el filsofo registra qu vecinos le han pedido un cubierto, para que cuando salga de la seccin crtica tenga conocimiento de a qu vecinos y en qu orden prestara sus cubiertos. Estos pasos se repiten indefinidamente en tanto estn corriendo los procesos de todos los filsofos que iniciaron el sistema distribuido.

Listing 1.5. init-CircToken.bat Start java NameServer Start "Filosofo 0" java LockTester "Filosofos" 0 5 CircToken Start "Filosofo 1" java LockTester "Filosofos" 1 5 CircToken Start "Filosofo 2" java LockTester "Filosofos" 2 5 CircToken Start "Filosofo 3" java LockTester "Filosofos" 3 5 CircToken Start "Filosofo 4" java LockTester "Filosofos" 4 5 CircTokenStart "Filosofo 4" java LockTester "Filosofos" 4 5 DiningPhil

Figura 3. Un filsofo comiendo y pensando

Listing 1.6. init-lamport.bat Start java NameServer Start "Filosofo 0" java LockTester "Filosofos" 0 5 Lamport Start "Filosofo 1" java LockTester "Filosofos" 1 5 Lamport Start "Filosofo 2" java LockTester "Filosofos" 2 5 Lamport Start "Filosofo 3" java LockTester "Filosofos" 3 5 Lamport Start "Filosofo 4" java LockTester "Filosofos" 4 5 Lamport

Figura 4. Un filsofo comiendo y pensando

Listing 1.7. init-RicartAgrawala.bat Start java NameServer Start "Filosofo 0" java LockTester "Filosofos" 0 5 RicartAgrawala Start "Filosofo 1" java LockTester "Filosofos" 1 5 RicartAgrawala Start "Filosofo 2" java LockTester "Filosofos" 2 5 RicartAgrawala Start "Filosofo 3" java LockTester "Filosofos" 3 5 RicartAgrawala Start "Filosofo 4" java LockTester "Filosofos" 4 5 RicartAgrawala

Figura 5. Un filsofo comiendo y pensando

3. Compilacin y ejecucin del programa


A continuacin, se listan los pasos para compilar y ejecutar el programa: Abra el archivo Symbols.java y cambie el valor de la constante nameServer, en la lnea 4, a la direccin de la mquina en la que correr el servidor de nombres. Por ejemplo: public static final String nameServer = localhost; Por cada proceso, cree un archivo que contenga los identificadores, cero- basados, de los vecinos del proceso. El nombre del archivo debe seguir el formato topology#, en donde # debe reemplazarse por el identificador del proceso al cual pertenece. Por ejemplo: Archivo: topology2 Contenido: 0 1 5 6 Muestra que los vecinos del proceso dos son 0, 1, 5 y 6. En una terminal, dirjase a la carpeta src y compile NameTable.java, NameServer.java y LockTester.java: javac NameTable.java NameServer.java LockTester.java Ejecute el servidor de nombres en segundo plano en una terminal de la siguiente forma: java NameServer & Para cada proceso, abra una terminal diferente e inicie el algoritmo con la siguiente instruccin: java LockTester NombreSimulacion Id NumProcesos Algoritmo , en donde Algoritmo puede tomar los valores Lamport, RicartAgrawala, DiningPhil y CircToken. Ejemplo: java LockTester Simulacin 0 4 CircToken Alternativamente, para probar el algoritmo de los filsofos comensales tal y cual se mostr en el ejemplo de este documento, en la carpeta src se incluye un sencillo script que ejecuta el servidor de nombres y cinco filsofos dispuestos en una topologa de anillo. Para correr el ejemplo teclee: ./probarDiningPhil

Referencias
1. Garg, Vijay. Concurrent and Distributed Computing in Java. Hoboken: IEEE Press/WileyInterscience, 2004. 2. Garg, Vijay. Distributed Computing: Java Code. http://users.ece.utexas.edu/garg/dist/jbkCode.html. Revisado el 25 de mayo de 2009.