19
Distribución de Recursos: Ejemplo del Algoritmo de los Filósofos Comensales Alberto Garcia-Robledo Laboratorio de Tecnologías de Información, CINVESTAV- Tamaulipas, Carretera Nacional Cd. Victoria-Monterrey Km 6, Cd. Victoria Tamaulipas, México, CP. 87276, Tel. (52 834) 316 6600 [email protected] 1. Introducción El presente documento muestra la ejecución de un sistema distribuido mínimo que ejemplifica el funcionamiento básico del algoritmo de los filósofos comensales. Tanto los programas de los componentes del sistema como del algoritmo fueron tomados de [1]. El código fuente puede ser descargado de [2], aunque se incluye distribuido junto con este documento. 2. Ejemplo de los filósofos comensales Cualquier implementación de algoritmo de exclusión mutua presentado en el capítulo 8 de [1] puede ser probado utilizando el programa del listado 1.1. La línea 8 crea un objeto Linker. La clase Linker, mostrada en el listado 1.2, permite enlazar un conjunto de procesos. A continuación 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 sólo saber el identificador, como se explica más adelante. Cada proceso lee la topología 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

Ejemplo DiningPhil

Embed Size (px)

Citation preview

Page 1: Ejemplo DiningPhil

Distribución de Recursos: Ejemplo del Algoritmo de los Filósofos

Comensales

Alberto Garcia-Robledo

Laboratorio de Tecnologías de Información, CINVESTAV-Tamaulipas, Carretera Nacional Cd. Victoria-Monterrey Km 6, Cd. Victoria Tamaulipas, México, CP. 87276, Tel. (52 834) 316 6600 [email protected]

1. Introducción

El presente documento muestra la ejecución de un sistema distribuido mínimo que ejemplifica el funcionamiento básico del algoritmo de los filósofos comensales. Tanto los programas de los componentes del sistema como del algoritmo fueron tomados de [1]. El código fuente puede ser descargado de [2], aunque se incluye distribuido junto con este documento.

2. Ejemplo de los filósofos comensales

Cualquier implementación de algoritmo de exclusión mutua presentado en el capítulo 8 de [1] puede ser probado utilizando el programa del listado 1.1. La línea 8 crea un objeto Linker. La clase Linker, mostrada en el listado 1.2, permite enlazar un conjunto de procesos. A continuación 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 sólo saber el identificador, como se explica más adelante. Cada proceso lee la topología 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 envío y recepción de mensajes. Detalles sobre la implementación de la clase Linker pueden ser encontrados en el capítulo 6 titulado Distributed Programming en [1].

Regresando a la explicación del listado 1.1, después de instanciar una implementación de algún algoritmo de exclusión mutua en las líneas 10 a 17, iniciamos un hilo que escucha los mensajes de otros procesos en las líneas 18 a 20. El bucle while de la línea 21 itera indefinidamente. En cada iteración el hilo que representa el proceso duerme por 2 segundos para luego solicitar acceso a la sección

Page 2: Ejemplo DiningPhil

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 Exception {Linker comm = n u l l ;

t r y {S t r i n g baseName = a r g s [ 0 ] ;int 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 Linker ( 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) ;( a r g s [ 3 ] . e q u a l s ( ” Ricart Agrawala ” ) ) l o c k = new RAMutex(comm) ;( a r g s [ 3 ] . e q u a l s ( ” Dining Phil ” ) ) l o c k = new DinMutex (comm) ;( a r g s [ 3 ] . e q u a l s ( ” CircToken ” ) )

i f

i f

i fl o c kfor ( inti f ( i

= new CircToken (comm, 0 ) ;i = 0 ; i < numProc ; i ++)!= myId )

( new Lis tener Thread ( i , ( MsgHandler ) l o c k ) ) .s t a r t ( ) ;

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 . reques t CS ( ) ;U t i l . mySleep ( 2000 ) ;

System . out . p r i n t l n ( myId + ” i s i n CS ∗∗∗∗∗ ” ) ;l o c k . r e l e a s e C S ( ) ;

}

}catch ( 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 . print Stack Trace ( ) ;

crítica utilizando la instancia del algoritmo seleccionado. Como se explicó en la exposición, este método bloquea el hilo hasta que, según el algoritmo elegido, es despertado dado a que se le ha concedido el acceso a la sección crítica. Después, se simula un procesamiento que tarda dos segundos durmiendo el hilo durante este tiempo. Finalmente, se libera el acceso a la sección crítica a través de la instancia del algoritmo seleccionado antes de iniciar otra iteración.

En resumen, el programa 1.1 instancia un algoritmo de exclusión mutua determinado, lanza un hilo que representa el proceso actual y entra a un bucle infinito. En cada iteración el hilo solicita acceso a la sección crítica utilizando la instancia del algoritmo elegido, se bloquea hasta obtener el acceso y simula un procesamiento que dura dos segundos antes de liberar la sección crítica y empezar una nueva iteración.

Listing 1.1. LockTester.java1

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

Page 3: Ejemplo DiningPhil

}}}

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 ;Buffered Reader [ ] data In ; Buffered Reader dIn ;int myId , N;Connector connector ;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 Buffered Reader [ numProc ] ; dataOut = new Print Wri ter [ numProc ] ; Topology . read Neighbors ( myId , N, n e i g h b o r s ) ; connector = new Connector ( ) ;connector . 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 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 receive Msg ( int from Id ) throws IOExceptionS 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 ) ;

tag ,

{

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 ( ) ) ;int 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 . nextToken ( ) ;s t . nextToken ( ”#” ) ;

S t r i n gS t r i n greturn

tag =msg =new Msg( s r c I d , dest Id , tag , msg ) ;

}

36

37

38

Listing 1.2. Linker.java1

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

Page 4: Ejemplo DiningPhil

42 p u b l i c int getMyId ( ) { return myId ; }43 p u b l i c int getNumProc ( ) { return N; }44 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 ( ) ; }45 }

El programa 1.1 es ejecutado de manera concurrente tantas veces como número de proceso queremos que tenga nuestro sistema distribuido. Al ejecutar cada instancia del programa, se le es asignado un identificador numérico 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 topología 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 filósofos 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. Topología del sistema distribuido que representa el a los filósofos 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 filósofos comensales. Como puede observarse, el primer paso es la ejecución 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

Page 5: Ejemplo DiningPhil

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 ( Socket t h e C l i e n t ) {

t r y {Buffered Reader din = new Buffered Reader( new Input Stream Reader ( t h e C l i e n t . get Input Stream ( ) )) ;Print Wri ter pout = new Print Wri ter ( 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 index = t a b l e . s e a r c h ( s t . nextToken ( ) ) ;i f ( index == −1) // not foundpout . p r i n t l n (−1 + ” ” + ” n u l l h o s t ” ) ;pout . p r i n t l n ( t a b l e . get Port ( index ) + ” ”+ t a b l e . getHostName ( index ) ) ;

else

} 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 port = I n t e g e r . p a r s e I n t ( s t . nextToken ( ) ) ;int ret Value = t a b l e . i n s e r t ( name , hostName , port ) ;pout . p r i n t l n ( ret Value ) ;}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 ) {

saber sobre los demás. El servidor de nombres mantiene una tabla con los campos name, hostName, portNumber a través 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 básicas que proporciona son insert (agregar proceso a la tabla) y search (buscar proceso en la tabla). Detalles sobre la im- plementación del servidor de nombres pueden ser encontrados en el capítulo en [1].

Listing 1.3. NameServer.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

Page 6: Ejemplo DiningPhil

NameServer ns = new NameServer ( ) ;System . out . p r i n t l n ( ” NameServer s t a r t e d : ” ) ; t r 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 .Server Port ) ;while ( t r u e ) {Socket a C l i e n t = l i s t e n e r . accept ( ) ;ns . h a n d l e c l i e n t ( a C l i e n t ) ; a C l i e n t . c l o s e ( ) ;}} catch ( IOException e ) {System . e r r . p r i n t l n ( ” S erver aborted : ” + e ) ;}

}}

37

38

39

40

41

42

43

44

45

46

47

48

49

50

A cada proceso se le indica mediante los argumentos del programa el identificador del proceso, el número de procesos total (5 en nuestro caso) y el nombre del algoritmo de exclusión mutua que deseamos que utilice. Las claves de los posibles algoritmos son:

Lamport RicartAgrawalaDiningPhil (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 ejecución de los 5 procesos filósofos corriendo de manera concurrente y sincronizada por el algoritmo de los filósofos comensales.

Page 7: Ejemplo DiningPhil

Figura 2. Un filósofo comiendo y pensando

En otras palabras, es posible ver a los cinco filósofos sentados en la mesa comiendo y pensando de manera sincronizada (aunque solo sea de manera textual). En la consola de cada filósofo es posible observar de manera secuencial cada uno de los mensajes de sincronización que llegan y salen de cada uno de ellos. Cuando un filósofo tiene hambre, pide sus cubiertos solo a aquellos filósofos que los comparten con ´el (sus dos vecinos en nuestro caso), por lo que les envía el mensaje request.

El filósofo se bloque en espera de la respuesta de ambos filósofos, es decir, hasta que recibe un mensaje Fork de cada uno de sus vecinos. Es entonces cuando entra a la sección crítica durante dos segundos antes de soltar los cubiertos. Mientras come, el filósofo registra qué vecinos le han pedido un cubierto, para que cuando salga de la sección crítica tenga conocimiento de a qué vecinos y en qué orden prestara´ sus cubiertos. Estos pasos se repiten indefinidamente en tanto estén corriendo los procesos de todos los filósofos que iniciaron el sistema distribuido.

Page 8: Ejemplo DiningPhil

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 filósofo comiendo y pensando

Page 9: Ejemplo DiningPhil
Page 10: Ejemplo DiningPhil

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 filósofo comiendo y pensando

Page 11: Ejemplo DiningPhil

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 filósofo comiendo y pensando

Page 12: Ejemplo DiningPhil

3. Compilación y ejecución del programa

A continuación, 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 línea 4, a la dirección de la máquina 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 6Muestra que los vecinos del proceso dos son 0, 1, 5 y 6.En una terminal, diríjase a la carpeta src y compile NameTable.java, Name- Server.java y LockTester.java:javac NameTable.java NameServer.java LockTester.javaEjecute 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 instrucción:java LockTester NombreSimulacion Id NumProcesos Algoritmo, en donde Algoritmo puede tomar los valores Lamport, RicartAgrawala, DiningPhil y CircToken. Ejemplo:java LockTester Simulación 0 4 CircToken

Alternativamente, para probar el algoritmo de los filósofos 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 filósofos dispuestos en una topología de anillo. Para correr el ejemplo teclee:./probarDiningPhil

Page 13: Ejemplo DiningPhil

Referencias

1. Garg, Vijay. Concurrent and Distributed Computing in Java. Hoboken: IEEE Press/Wiley-Interscience, 2004.

2. Garg, Vijay. Distributed Computing: Java Code. http://users.ece.utexas.edu/g˜arg/dist/jbkCode.html. Revisado el 25 de mayo de 2009.