You are on page 1of 10

Diseo de Algoritmos Distribuidos en Java

Jos Luis Pastrana Brincones (pastrana@lcc.uma.es)


Departamento de Lenguajes y Ciencias de la Computacin de la Universidad de Mlaga
Resumen
El diseo de algoritmos es una parte fundamental en las tecnologas de la
informacin, como se ha visto reflejado por el gran nmero de libros y artculos
relacionados con el tema. La gran mayora de los mismos, tratan los algoritmos en el
contexto de una programacin secuencial, donde el flujo de control va siempre en una
misma direccin y en cada paso del algoritmo se realiza una nica accin. El gran auge
del concepto de redes de comunicacin, junto con los avances en la metodologa de
programacin han conseguido que el concepto de comunicacin y de diseo de
algoritmos distribuidos surjan como un nuevo aspecto en las tcnicas de desarrollo del
software.
Los algoritmos distribuidos, clsicamente, han sido desarrollados mediante el
uso de lenguajes imperativos a los que se les ha aadido determinadas primitivas de
comunicacin, pero si nos fijamos en la filosofa de los lenguajes orientados a objetos,
podemos ver la similitud existente entre procesos y objetos, comunicacin y mtodos, y
an ms, incluso podramos ver nuestra red de comunicaciones como un objeto ms en
nuestro sistema. En este trabajo, consideraremos dos clases de algoritmos distribuidos:
la primera en la que trataremos los algoritmos basados en una arquitectura
Cliente/Servidor y para la que usaremos una tcnica de invocacin de mtodos remotos.
Y una segunda ,ms genrica, en la que consideraremos un sistema distribuido como un
conjunto de objetos o procesos conectados a travs de una red y que cooperan para la
obtencin de un objetivo comn. Para la resolucin de este tipo de problemas,
consideraremos la red como un objeto ms del sistema que tiene mtodos para el envo
y recepcin de mensajes a travs de la misma.
A lo largo del trabajo, se describirn ambas tcnicas y se implementarn varios
algoritmos como ejemplo para su mejor comprensin. Como lenguaje de soporte hemos
usado Java debido al gran auge que est teniendo en nuestros das, as como por
adaptarse a los requerimientos de orientacin a objetos e incluir un paquete para la
invocacin de mtodos remotos, y la clase jPVM (David A. Thurman) que embebe PVM
en Java y que nos ha servido para modelar la red como un objeto del sistema.
Arquitectura Cliente/Servidor.
En una arquitectura Cliente/Servidor, el servidor es un proceso que repetidamente
maneja peticiones de los clientes (recibe una peticin de un cliente, realiza el servicio y retorna
el resultado), y el cliente es el proceso que realiza dichas peticiones de servicios a otra
aplicacin llamada cliente.
El desarrollo de aplicaciones Cliente/Servidor mediante sockets conlleva el diseo de
un protocolo que consiste en un lenguaje comn entre el Cliente y el Servidor. El diseo de
dicho protocolo es una de las mayores fuentes de errores tales como el deadlock. En lugar de
trabajar directamente con sockets, una aplicacin Cliente/Servidor puede ser desarrollada
usando una filosofa de orientacin a objetos. Desde el punto de vista del Cliente,
consideramos el Servidor como un objeto remoto que nos ofrece una serie de servicios que
pueden ser llamados mediante la invocacin remota de sus mtodos. La invocacin de
mtodos remotos (Remote Methods Invocation, RMI) es muy similar (pero ms general y
fcil de usar) que la llamada a procedimientos remotos (Remote Procedure Call, RPC).
Bsicamente, el usuario trabaja con los objetos remotos como si estos fueran locales, gracias
a que los paquetes RMI incluyen un recolector de basura distribuido y una invocacin
transparente de los mtodos remotos (con la misma sintaxis que los locales).
Veamos a continuacin cmo podramos realizar esto mediante un sencillo ejemplo
realizado en Java. Vamos a desarrollar un Servidor que nos realiza operaciones sobre
vectores y matrices, as como un cliente que las utiliza. El desarrollo de aplicaciones
Cliente/Servidor en Java conlleva 6 etapas:
1. Definicin de la Interfaz Remota. Permite al cliente conocer los servicios que puede utilizar,
por lo tanto debe ser pblica.
2. Implementacin de la interfaz remota. Esta ser el cuerpo del servidor.
3. Escribir la aplicacin cliente que use el servidor.
4. Generar los stubs y skeletons. Un stub es un cliente proxy y un skeleton es una entidad
servidor que realiza las llamadas a la implementacin actual del objeto remoto
5. Ejecutar el registry. Es un Servidor de nombres.
1. Ejecutar el Servidor y el/los Cliente.
Remote interface.
public interface MatrixInterface extends java.rmi.Remote
{ public int[] add(int a[], int b[] ) throws java.rmi.RemoteException;
public int[] sub(int a[], int b[] ) throws java.rmi.RemoteException;
....................................................................................................................
public int[][] add(int a[][], int b[][] ) throws java.rmi.RemoteException;
public int[][] sub(int a[][], int b[][] ) throws java.rmi.RemoteException;
....................................................................................................................}
Remote interface Implementation
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
public class MatrixServer extends UnicastRemoteObject implements MatrixInterface
{ private String ServerName;
public MatrixServer ( String s ) throws RemoteException
{ ServerName = s; }
public int[] add(int a[], int b[] ) throws RemoteException
{ ................... }
public int[] sub(int a[], int b[] ) throws java.rmi.RemoteException
{ ................... }
.................................................................................................................
public int[][] add(int a[][], int b[][] ) throws java.rmi.RemoteException
{ ................... }
public int[][] sub(int a[][], int b[][] ) throws java.rmi.RemoteException
{ ................... }
.................................................................................................................
public static void main(String argv[])
{ MatrixServer server;
System.setSecurityManager( new RMISecurityManager() );
try
{ server = new MatrixServer ( " MatrixServer " );
Naming.rebind("//host/ MatrixServer ",server);
System.out.println("MatrixServer running ");
}
catch (Exception e)
{ System.out.println("Sorry, error succeeded:" + e.getMessage());
e.printStackTrace();
}
}
}
Cliente Application
import java.rmi.*;
import java.net.*;
public class Cliente
{ public static void main(String argv[])
{ int a[] = { 1, 2, 3, 4, 5 };
int b[] = { 6, 7, 8, 9, 0 };
int result [] = new int[5];
MatrixInterface RemoteServer;
int i;
try
{ String url = "//host/MatrixServer";
RemoteServer = (MatrixInterface)Naming.lookup(url);
result = RemoteServer.add(a,b);
System.out.print(();
for(i=0;i<4;++i) System.out.print(a[i] + , );
System.out.print(a[4] + ) + ( );
for(i=0;i<4;++i) System.out.print(b[i] + , );
System.out.print(b[4] + ) = ( );
for(i=0;i<4;++i) System.out.print(c[i] + , );
System.out.println(c[4] + ));
}
catch (Exception e)
{ System.out.println("Sorry, error succeeded:" + e.getMessage());
e.printStackTrace();
}
}
}
Conjunto e Procesos que Cooperan para lograr un Objetivo Comn.
Un algoritmo distribuido ha sido definido clsicamente como un conjunto de procesos,
conectados a travs de una red, que cooperan para lograr un objetivo comn. Nuestra
aproximacin consiste en considerar cada proceso como un objeto, e incluso la red de
conexin es considerada como un objeto ms de nuestro sistema. Cada objeto-proceso tiene
sus propios mtodos y objetos locales para la resolucin de su parte de la tarea y la red es un
objeto especial que tiene mtodos para el envo y recepcin de mensajes entre objetos.
Veamos cmo podemos utilizar esta aproximacin para la resolucin de un problema
clsico para sistemas distribuidos: El Problema de la Exclusin Mutua Distribuida. Hemos
utilizado Java como soporte junto con la clase jPVM (desarrollada por David A. Thurman,
Center for Human-Machine Systems Research, School of Industrial and Systems Engineering,
Georgia Institute of Technology) para el modelado de la red como objeto. Esta clase
implementa todas las primitivas de las libreras de PVM mediante la incorporacin de mtodos
en cdigo nativo.
El Problema de la Exclusin Mutua Distribuida.
Este es uno de los primeros problemas que se plantean en programacin distribuida.
Una serie de procesos, que trabajan en paralelo, compiten por recursos que no pueden ser
accedidos al mismo tiempo. El problema de la exclusin mutua fue debido a Dekker y usaba
nicamente operaciones primitivas para la lectura y escritura de una palabra en memoria. En
un sistema distribuido, la especificacin de un algoritmo de exclusin mutua no puede ser
realizada en trminos de dependencia de un acceso a memoria central, debe ser realizada en
trminos de intercambio de mensajes, y el algoritmo debe tener las caractersticas de
equitabilidad y ausencia de bloqueo, es decir, que cualquier proceso que desee entrar en su
seccin crtica debe ser capaz de hacerlo en un intervalo finito de tiempo.
Circulacin de un Token en un Anillo Lgico.
Este es un algoritmo simple para el problema de la exclusin mutua distribuida,
aplicable cuando la topologa de nuestra red es un anillo. Cada proceso solamente podr
entrar en su seccin crtica cuando posea el Token, que siempre recibir de su vecino de la
izquierda, y dar al de su derecha al salir de la seccin crtica.
public class TokenRing
{
public static void main(String argv[])
{ Net net;
int max;
int me;
int left;
int right;
boolean token_present;
try { Net = new Net((Integer.valueOf
(argv[0])).intValue()); }
catch (ArrayIndexOutOfBoundsException
e) { Net = new Net(1); }
me = net.mi_id();
max = net.numProcs();
right = (me+1)%max;
left = me -1;
if (left<0) left = max -1;
if (me==0) token_present=true;
else token_present = false;
/* Algorithm Body */
while(true)
{
if (!token_present)
{net.wait_token(left);
token_present=true;
}
/* Critical Section */
System.out.println("Process "+me+
" in Critical Section");
/* Critical Section */
net.send_token(right);
token_present=false;
}
}
}
public class Net extends jPVM
{ private int my_id;
private int my_father;
private int maxProc;
private int processes[];
private Net pvm;
public final static int Initialise = 0;
public final static int Token = 1;
public Net() { super(); }
public Net(int max)
{ String javaexe ="/jdk/bin/java";
String args[];
int max_array[];
int i;
args = new String [3];
args[0]= "-classpath";
args[1]= " /jdk/lib/classes.zip
+ :/jPVM-v1.1.4:/TokenRing_pvm";
args[2]="TokenRing";
max_array = new int[1];
pvm = new Net();
my_id = pvm.mytid();
my_father = pvm.parent();
if (my_father==PvmNoParent)
{ maxProc = max;
processes = new int[maxProc];
processes[0]=my_id;
for(i=1;i<maxProc;++i)
{ pvm.spawn(javaexe,args,
PvmTaskDefault,"",max_array);
processes[i]=max_array[0];
}
max_array[0] = maxProc;
pvm.initsend(PvmDataDefault);
pvm.pkint(max_array, 1, 1);
pvm.pkint(processes, maxProc, 1);
for(i=1;i<maxProc;++i)
pvm.send(processes[i], Initialise);
}
else
{ pvm.recv(my_father, Initialise);
pvm.upkint(max_array, 1, 1);
maxProc = max_array[0];
processes = new int[maxProc];
pvm.upkint(processes, maxProc, 1);
}
}
public int mi_id()
{ int i;
for (i=0;i<maxProc;++i)
if (processes[i]==my_id) return i;
return -1;
}
public int numProcs()
{ return maxProc; }
public void wait_token(int left)
{ pvm.recv(processes[left],Token); }
public void send_token(int right)
{ pvm.initsend(PvmDataDefault);
pvm.send(processes[right],Token);
} }
El Algoritmo de Ricart and Agrawala/Suzuki Kasami .
En este algoritmo, el privilegio que permite a un proceso entrar en su seccin crtica
esta representado por un Token; cualquier proceso que posea el Token podr entrar en su
seccin crtica sin pedir permiso a los dems. Inicialmente el Token es asignado a uno de los
procesos (el proceso nmero cero en nuestro caso). Un proceso que desea entrar en su
seccin crtica no sabr qu otro proceso tiene el Token en dicho instante y lo pedir
mediante la difusin todos los dems de un mensaje que estar etiquetado en el tiempo.
Cuando el proceso que tiene el Token (Pj) deja su seccin crtica, busca en el vector de
peticiones en orden j+1,j+2,...,n,1,2,...j-1 el primer valor de k tal que su etiqueta de tiempo de su
ltima peticin del Token sea mayor que el valor almacenado en el Token para dicho proceso,
y entonces transfiere el Token de Pj a Pk.
public class Ricart
{ public static void main(String argv[])
{ Net net;
int max,me,i,j clock,token[],requests[];
boolean token_present, token_held;
try { net = new Red( (Integer.valueOf
(argv[0])).intValue()); }
catch (ArrayIndexOutOfBoundsException
e ) { net = new Red(1); }
me = net.mi_id();
max = net.numProcs();
if (me==0) token_present=true;
else token_present = false;
token_held=false;
clock=0;
token = new int[max];
requests = new int[max];
for(i=0;i<max;++i)
{ token[i]=0;
requests[i]=0;
}
/* Algorithm Body */
while(true)
{ if (!token_present)
{ ++clock;
net.broadcast(requests,clock);
token = net.wait_access();
token_present=true;
}
token_held=true;
/* Critical Section */
System.out.println("Process "+me+
" in Critical Section");
/* Critical Section */
token[me] = clock;
token_held=false;
requests = net.wait_request();
if (requests!=null) /* any request */
for(i=0;i<max;++i)
{ j = (i+me+1)%max;
if ( (requests[j] > token[j]) &&
(token_present) )
{ token_present=false;
net.send_access(token,j);
break;
}
}
}
}
}
public class Net extends jPVM
{ private int my_id,my_father,maxProc,
processes[];
private Net pvm;
public final static int Initialise = 0;
public final static int Request = 1;
public final static int Access = 2;
public final static int Any = -1;
public Net() { super(); }
public Net(int max)
{ String javaexe = "/jdk/bin/java";
String args[];
int max_array[],i;
args = new String [3];
args[0]= "-classpath";
args[1]= "/jdk/lib/classes.zip:+
/jPVM-v1.1.4: /Ricart_pvm";
args[2]="Ricart";
max_array = new int[1];
pvm = new Net();
my_id = pvm.mytid();
my_father = pvm.parent();
if (my_father==PvmNoParent)
{ maxProc = max;
processes = new int[maxProc];
processes[0]=my_id;
for(i=1;i<maxProc;++i)
{ pvm.spawn(javaexe,args,
PvmTaskDefault,"",max_array);
processes[i]=max_array[0];
}
max_array[0] = maxProc;
pvm.initsend(PvmDataDefault);
pvm.pkint(max_array, 1, 1);
pvm.pkint(processes, maxProc, 1);
for(i=1;i<maxProc;++i)
pvm.send(processes[i],Initialise);
}
else
{ pvm.recv(my_father,Initialise);
pvm.upkint(max_array, 1, 1);
maxProc = max_array[0];
processes = new int[maxProc];
pvm.upkint(processes, maxProc, 1);
}
}
public int mi_id()
{ int i;
for (i=0;i<maxProc;++i)
if (processes[i]==my_id) return i;
return -1;
}
public int numProcs()
{ return maxProc; }
public void broadcast(int requests[],int clock)
{ int i,ck[],me[];
me = new int[1];
me[0] = mi_id();
ck = new int[1];
ck[0]=clock;
pvm.initsend(PvmDataDefault);
pvm.pkint(me, 1, 1);
pvm.pkint(ck, 1, 1);
pvm.pkint(requests, maxProc, 1);
for (i=0;i<maxProc;++i)
if (processes[i]!=my_id)
pvm.send(processes[i],Request);
}
public int[] wait_request()
{ int rq[],info,ck[],me[];
info = pvm.nrecv(Any,Request);
if (info<=0) return null; /* No requests */
rq = new int[maxProc];
me = new int[1];
ck = new int[1];
pvm.upkint(me, 1, 1);
pvm.upkint(ck, 1, 1);
pvm.upkint(rq, maxProc, 1);
if (rq[me[0]] < ck[0]) rq[me[0]] = ck[0];
return rq;
}
public int[] wait_access()
{ int token[];
token = new int[maxProc];
pvm.recv(Any,Access);
pvm.pkint(token, maxProc, 1);
return token;
}
public void send_access(int token[],int j)
{ pvm.initsend(PvmDataDefault);
pvm.pkint(token, maxProc, 1);
pvm.send(processes[j],Access);
}
}
Prdida del Token: Algoritmo de Misra.
El principal problema de la solucin de un Token circulando por un anillo es que la
prdida de dicho Token implica un bloqueo del sistema. El Algoritmo de Misra fue
desarrollado para la deteccin y regeneracin del Token en el caso de que ste se pierda.
Usaremos dos tokens, llamados ping y pong, y asociados con ellos dos nmeros enteros
llamados nbping y nbpong respectivamente, iguales en valor absoluto pero de signo opuesto,
que almacenan el nmero de veces que ambos tokens se han encontrado en un mismo
proceso. Dichos nmeros estarn entonces relacionados por la restriccin nbping + nbpong
= 0. Inicialmente, ambos tokens estn en un mismo proceso y por tanto, nbping = 1, nbpong
= -1. Cada proceso Pi almacena en una variable local, inicializadas a cero, el valor de nbping
o nbpong, asociado con el Token. Esencialmente, el algoritmo conserva la relacin nbping +
nbpong = 0 y cuando esta relacin se rompe, significa que el Token ping (o pong) se ha
perdido y hay que regenerarlo.
public class Misra
{ public final static int ping = 0;
public final static int pong = 1;
public static void main(String argv[])
{ Net net;
int max,me,left,right,
nbping=1,nbpong=-1,m=0;
boolean token_ping,token_pong;
try { net = new net( (Integer.valueOf
(argv[0])).intValue()); }
catch (ArrayIndexOutOfBoundsException
e) { net = new net(1); }
me = net.mi_id();
max = net.numProcs();
right = (me+1)%max;
left = me -1;
if (left<0) left = max -1;
if (me==0) { token_ping=true;
token_pong=true; }
else { token_ping = false;
token_pong = false; }
/* Algorithm Body */
while(true)
{ if (!token_ping) token_ping =
net.wait_token(left,ping,nbping);
if (!token_pong) token_pong =
net.wait_token(left,pong,nbpong);
if (token_ping) /* Critical Section */
{ System.out.println("Process "+me
+" in Critical Section ");
}
if ((token_ping) && (token_pong) )
{ ++ nbping;
-- nbpong;
net.send_token(right,ping,nbping);
net.send_token(right,pong,nbpong);
}
else
{ if (token_ping)
{ if (m == nbping) /* Token Lost */
{ ++nbping;
nbpong = -nbping;
net.send_token(right,pong,
nbpong); /* Regenerate it */
}
else { m = nbping; }
net.send_token(right,ping,nbping);
}
if (token_pong)
{ if (m == nbpong) /* Token Lost */
{ ++nbpong;
nbping = -nbpong;
net.send_token(right,ping,
nbping); /* Regenerate it */
}
else { m = nbpong; }
net.send_token(right,pong,nbpong);
}
}
}
}
}
public class Net extends jPVM
{ private int my_id,my_father,
maxProc,processes[];
private Net pvm;
public final static int Initialise = 0;
public Net() { super(); }
public Net(int max)
{ String args[],javaexe = "/jdk/bin/java";
int i,max_array[];
args = new String [3];
args[0]= "-classpath";
args[1]= "/jdk/lib/classes.zip+
:/jPVM-v1.1.4:/Misra_pvm";
args[2]="Misra";
max_array = new int[1];
pvm = new Net();
my_id = pvm.mytid();
my_father = pvm.parent();
if (my_father==PvmNoParent)
{ maxProc = max;
processes = new int[maxProc];
processes[0]=my_id;
for(i=1;i<maxProc;++i)
{ pvm.spawn(javaexe,args,
PvmTaskDefault,"",max_array);
processes[i]=max_array[0];
}
max_array[0] = maxProc;
pvm.initsend(PvmDataDefault);
pvm.pkint(max_array, 1, 1);
pvm.pkint(processes, maxProc, 1);
for(i=1;i<maxProc;++i)
pvm.send(processes[i],Initialise);
else
{ pvm.recv(my_father,Initialise);
pvm.upkint(max_array, 1, 1);
maxProc = max_array[0];
processes = new int[maxProc];
pvm.upkint(processes, maxProc, 1);
}
}
public int mi_id()
{ int i;
for (i=0;i<maxProc;++i)
if (processes[i]==my_id) return i;
return -1;
}
public int numProcs() { return maxProc; }
public boolean wait_token(int i,int tag, int nb)
{ int info,numb[];
info = pvm.nrecv(processes[i],tag);
if (info<=0) return false;
numb = new int[1];
pvm.upkint(numb,1,1);
nb = numb[0];
return true;
}
public void send_token(int i,int tag, int nb)
{ int numb[];
numb = new int[1];
numb[0] = nb;
pvm.initsend(PvmDataDefault);
pvm.pkint(numb,1,1);
pvm.send(processes[i],tag);
}
}
Referencias
[1] M. Raynal, Distributed Algorithms and Protocols: John Wiley & Sons Ltd., 1988.
[2] R. Andrews, Concurrent Programming. Gregory: Benjamin/Cummings, 1991.
[3] Remote Method Invocation System. Sun Microsystems Inc. ,1996-1997.
[4] Getting Started using RMI. Sun Microsystems Inc. ,1996-1997.
[5] RMI and Object Serialisation. Sun Microsystems Inc. ,1996-1997.
[6] Q. H. Mahmond, Distributed Object Programming Using Java:Java Resource
Center,1997.

You might also like