La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

17-5-20041 Concurrencia en JAVA JAVA es un lenguaje que tiene soporte de concurrencia, mediante Threads. Un thread es un proceso liviano (lightweight process)

Presentaciones similares


Presentación del tema: "17-5-20041 Concurrencia en JAVA JAVA es un lenguaje que tiene soporte de concurrencia, mediante Threads. Un thread es un proceso liviano (lightweight process)"— Transcripción de la presentación:

1 Concurrencia en JAVA JAVA es un lenguaje que tiene soporte de concurrencia, mediante Threads. Un thread es un proceso liviano (lightweight process) que tiene un contexto privado mínimo. Cada Thread en JAVA tiene su propio contador de programa, su pila de ejecución (stack) y su conjunto de registros (working set), pero la zona de datos es compartida por todos los threads, exigiendo sincronización. A continuación se comenta una biblioteca de ejemplos en JAVA desarrollada en el 2002 por Andrés Barbieri. Todos los fuentes están disponibles en la página de la cátedra. Programación Concurrente Clase 6

2 Threads en JAVA Para crear Threads existen 2 formas mostradas en los ejemplos: FoolThread.java FoolRunnable.java Estas son dos clases que muestran los pasos para la creación y ejecución de threads. La primera usa una subclase de la clase de la API llamada "Thread" y la segunda muestra una implementación de la interfaz "Runnable". Más detalles en los comentarios del código fuente. Programación Concurrente Clase 6

3 Threads en JAVA Exclusión mútua implícita: El mecanismo de sincronización o exclusión mutua más básico es el provisto por la palabra clave "synchronized" declarada sobre un método sobre un conjunto de sentencias. Cada objeto tiene lo que se llama un "lock" y para ganar acceso a éste se lo hace mediante un bloque protegido por la palabra "synchronized", por lo tanto para obtenerlo deberá esperar hasta tener el acceso exclusivo el cual retendrá hasta salir del bloque. La clase ImplicitMutex muestra un ejemplo de uso. ImplicitMutex.java Programación Concurrente Clase 6

4 Threads en JAVA Exclusión mútua explícita JAVA permite la sincronización por condición con los métodos wait, notify y notifyAll que funcionan de un modo similar al wait y signal que vimos en monitores. Si bien no existen las variables "condition" los "lock" implícitos en cada objeto cumplen esta función. Los métodos son definidos en la superclase "Object" y siempre deben ser ejecutados en porciones de código "synchronized". La semántica que tienen es de SC (Signal and Continue). Ejemplos: ProtectedVar.java MonitoredVar.java Programación Concurrente Clase 6

5 Threads en JAVA Lectores escritores en JAVA se puede ver en: UsefulThread.java y la aplicación que arranca todo es: TestUseful.java Otros ejemplos que están en la biblioteca: Semaphore.java + ExplicitMutex.java (Implementación de semáforos) SyncMessageChannel.java + TestSync.java (Implementación de mensajes sincrónicos) AsyncMessageChannel.java + TestAsync.java (Implementación de mensajes asincrónicos) Programación Concurrente Clase 6

6 Pasaje de mensajes en JAVA En una arquitectura distribuida tenemos pasaje de mensajes. EN particular la comunicación entre procesos proveída por TCP/IP es el protocolo más utilizado actualmente, tanto en LANs como WANs. La API de programación para TCP/IP más conocida son los sockets, introducidos por UNIX. JAVA ofrece también un conjunto de clases para implementar esta interfaz. En general se usan sockets TCP los cuales fueron concebidos para un arquitectura Cliente/Servidor, por eso los ejemplos que se mencionan. Programación Concurrente Clase 6

7 Pasaje de mensajes en JAVA En la biblioteca se encuentra el código de: FindClient.java (Cliente) FIndServer.java (Servidor) Se trata de un ejemplo muy simple de interacción cliente- servidor por mensajes en JAVA. El Cliente envía una palabra y le pide al servidor que le cuente la cantidad de letras A de la misma, recibiendo el número como respuesta. Programación Concurrente Clase 6

8 Ejemplo con Pthreads y memoria compartida Un número de threads (numWorkers) suman los elementos de una matriz compartida con size filas y columnas. Matrix ( [size], [size]) Se trata de un caso típico de paralelismo iterativo con memoria compartida. #include #define SHARED 1 #define MAXSIZE 2000 /* maximum matrix size */ #define MAXWORKERS 4 /* maximum number of workers */ pthread_mutex_t barrier; /* lock for the barrier */ pthread_cond_t go; /* condition variable */ int numWorkers; /* number of worker threads */ int numArrived = 0; /* number who have arrived */ Programación Concurrente Clase 6

9 Suma paralela de los elementos de una matriz con Pthreads /* a reusable counter barrier */ void Barrier() { pthread_mutex_lock(&barrier); numArrived++; if (numArrived < numWorkers) pthread_cond_wait(&go, &barrier); else { numArrived = 0; /* last worker awakens others */ pthread_cond_broadcast(&go); } pthread_mutex_unlock(&barrier); } Programación Concurrente Clase 6

10 Suma paralela de los elementos de una matriz con Pthreads void *Worker(void *); int size, stripSize; /* size == stripSize*numWorkers */ int sums[MAXWORKERS]; /* sums computed by each worker */ int matrix[MAXSIZE][MAXSIZE]; /* read command line, initialize, and create threads */ int main(int argc, char *argv[ ]) { int i, j; pthread_attr_t attr; pthread_t workerid[MAXWORKERS]; /* set global thread attributes */ pthread_attr_init(&attr); pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); Programación Concurrente Clase 6

11 Suma paralela de los elementos de una matriz con Pthreads /* initialize mutex and condition variable */ pthread_mutex_init(&barrier, NULL); pthread_cond_init(&go, NULL); /* read command line */ size = atoi(argv[1]); numWorkers = atoi(argv[2]); stripSize = size/numWorkers; /* initialize the matrix */ for (i = 0; i < size; i++) for (j = 0; j < size; j++) matrix[ i ] [ j ] = 1; Programación Concurrente Clase 6

12 Suma paralela de los elementos de una matriz con Pthreads /* create the workers, then exit main thread */ for (i = 0; i < numWorkers; i++) pthread_create(&workerid[ i ], &attr, Worker, (void *) i); pthread_exit(NULL); } /* Each worker sums the values in one strip. After a barrier, worker(0) prints the total */ void *Worker(void *arg) { int myid = (int) arg; int total, i, j, first, last; /* determine first and last rows of my strip */ first = myid*stripSize; last = first + stripSize - 1; Programación Concurrente Clase 6

13 Suma paralela de los elementos de una matriz con Pthreads /* sum values in my strip */ total = 0; for (i = first; i <= last; i++) for (j = 0; j < size; j++) total += matrix[ i ][ j ]; sums[myid] = total; Barrier(); if (myid == 0) { /* worker 0 computes the total */ total = 0; for (i = 0; i < numWorkers; i++) total += sums[ i ]; printf("the total is %d\n", total); } Programación Concurrente Clase 6

14 Kernel monoprocesador Un kernel o núcleo es un pequeño conjunto de estructuras de datos y procedimientos que forman el core de cualquier programa concurrente (en particular de un sistema operativo como administrador de recursos) Las estructuras de datos representan el estado de los procesos, semáforos y variables de condición. Las subrutinas o procedimientos representan operaciones primitivas sobre las estructuras de datos. La palabra primitiva se asocia con la ejecución atómica. So; CO P1 // P2 //.... Pn OC Sn+1; Programación Concurrente Clase 6

15 Kernel monoprocesador Para implementar un código concurrente como el anterior se requieren tres mecanismos: CREAR procesos y arrancar su ejecución. STOP y eliminación de procesos. Saber cuando termina el alcance de un CO. Una primitiva es una rutina que es implementada por el Kernel, de tal modo que se ejecuta en forma atómica. Programación Concurrente Clase 6

16 Kernel monoprocesador FORK es la primitiva para crear otro proceso (hijo o child) que queda elegible para ejecución. El FORK tendrá como parámetro la dirección inicial de ejecución y datos de estado inicial. QUIT cuando un proceso lo ejecuta, se elimina. JOIN es la primitiva para esperar que todos los procesos involucrados en un CO lleguen al punto del OC (es decir terminen la parte concurrente de su ejecución). Normalmente un padre puede hacer JOIN de uno, varios o todos sus hijos. Programación Concurrente Clase 6

17 Kernel monoprocesador So; FOR [i=1 to n] # creamos los procesos hijos FORK (Pi); FOR [i=1 to n] # esperamos el fin de cada uno de ellos JOIN (Pi); Sn+1; Suponemos que el proceso principal está creado implícitamente e inicia la ejecución. A su vez el código de cada Pi ya está en memoria para ejecutarse. Al ejecutar el segundo FOR debe esperar que termine P1, luego P2 y así sucesivamente. Podría ser JOIN sin argumentos = espera sin orden que terminen todos. Programación Concurrente Clase 6

18 Kernel monoprocesador Un Kernel puede organizarse de un modo monolítico en el cual cada primitiva del kernel se ejecuta en forma atómica (y sólo una en un instante de tiempo) lo usamos en el single processor kernel. Un Kernel puede organizarse como un programa concurrente donde los procesos usuario pueden ejecutar diferentes primitivas del Kernel en el mismo instante de tiempo (concepto de re-entrante). monoprocesador / multiprocesador Programación Concurrente Clase 6

19 Kernel monoprocesador Cada proceso está representado en el Kernel por un descriptor. En el descriptor está el estado del proceso y su contexto (registros propios y dirección de la próxima instrucción a ejecutar). El Kernel arranca cuando se produce una interrupción (externa, por ejemplo de un periférico o interna, trap o SVC ejecutada por un proceso para invocar un servicio del kernel). Programación Concurrente Clase 6

20 Kernel monoprocesador El manejador de interrupciones analiza el pedido de interrupción y deriva la ejecución a la primitiva correspondiente. Cuando la primitiva se completa, un proceso dispatcher o scheduler decide que proceso continúa ejecutándose (context switching). En el kernel que sigue, el arreglo ProcessDescriptor[maxProcs] tendrá los descriptores de procesos, entre ellos la variable executing que nos dará el estado del proceso. Programación Concurrente Clase 6

21 Kernel monoprocesador Para asegurar que las primitivas se ejecutan atómicamente, la primer acción del manejador de interrupciones es inhibir otras interrupciones. El dispatcher rehabilitará las interrupciones, cuando la primitiva termine y le devuelva el control. En máquinas con múltiples niveles de interrupciones, podría no ser necesario bloquear todas. Programación Concurrente Clase 6

22 Kernel monoprocesador. Comentarios El arreglo ProcessDescriptor[maxprocs] tiene un tipo ProcessType que será una estructura de registro indicando los campos del descriptor. Un proceso padre le pide al kernel la creación de un proceso hijo, invocando la primitiva FORK que alocará e inicializará un descriptor vacío. Cuando el dispatcher busca un proceso para ejecutar, deberá buscar en la lista de procesos ready ya creados. Programación Concurrente Clase 6

23 Kernel monoprocesador. Comentarios Asumiremos dos listas FREE LIST de descriptores vacíos para responder a los FORK y READY LIST para indicar los procesos ya inicializados que están listos para que el dispatcher los elija. Una tercer lista WAITING indicará los procesos padre que están esperando procesos hijos que hagan Quit. La variable EXECUTING tendrá el valor del índice del descriptor del proceso que actualmente se está ejecutando. Al bootear se crea un proceso, se asigna su descriptor y su índice a EXECUTING y las listas Ready y Waiting están vacías. Programación Concurrente Clase 6

24 Kernel monoprocesador. Comentarios Con esta representación de los datos del Kernel, un Fork toma un descriptor de la lista Free, lo inicializa y lo inserta al final de la lista Ready. El Join chequea si el proceso hijo mencionado ha hecho Quit. Si no, deja el proceso padre en la lista Waiting. La ejecución de un Quit registra cual proceso terminó, pone el descriptor de ese proceso en la lista Free, despierta al proceso padre que esté esperando el Quit y setea Executing a 0, para indicarle al dispatcher que el proceso ha liberado el procesador. Programación Concurrente Clase 6

25 Kernel monoprocesador. Comentarios Cuando se invoca el dispatcher al final de una primitiva, chequea el valor de Executing. Si NO es 0, el proceso actual sigue reteniendo la ejecución. Si fuera 0, el dispatcher busca el primer descriptor de la lista Ready y setea con su índice a Executing. Luego carga el estado (conjunto de registros) de ese proceso. Si la lista Ready es FIFO y todos los procesos llegan al Quit en tiempo finito, el esquema asegura cierto fairness. Para evitar un deadlock, podemos usar un interval timer que es un dispositivo de hardware que dispara una interrupción luego de un tiempo fijo = el dispatcher lo puede usar para cambiar el Executing si es necesario... Programación Concurrente Clase 6

26 Ejemplo de implementación: Kernel monoprocesador. Programación Concurrente Clase 6 processType processDescriptor[maxProcs]; int executing = 0; # index of the executing process declarations of variables for the free, ready, and waiting lists; SVC_Handler: { # entered with interrupts inhibited save state of executing; determine which primitive was invoked, then call it; } Timer_Handler: { # entered with interrupts inhibited insert descriptor of executing at end of ready list; executing = 0; dispatcher( ); }

27 Ejemplo de implementación: Kernel monoprocesador. Programación Concurrente Clase 6 procedure fork(initial process state) { remove a descriptor from the free list and initialize it; insert the descriptor on the end of the ready list; dispatcher( ); } procedure quit( ) { record that executing has quit; insert descriptor of executing at end of free list; executing = 0; if (parent process is waiting for this child) { remove parent from the waiting list; put parent on the ready list; } dispatcher(); }

28 Ejemplo de implementación: Kernel monoprocesador. Programación Concurrente Clase 6 procedure join(name of child process) { if (child has not yet quit) { put the descriptor of executing on the waiting list; executing = 0; } dispatcher(); } procedure dispatcher() { if (executing == 0) { # current process blocked or quit remove descriptor from front of ready list; set executing to point to it; } start the interval timer; load state of executing; # with interrupts enabled }

29 Kernel multiprocesador Para extender el kernel monoprocesador a multiprocesador, debemos considerar que tenemos memoria compartida accesible por cualquier procesador, lo cuál obliga a algunas modificaciones simples en el kernel monoprocesador: 1- Los procedimientos y datos del kernel estarán en memoria compartida, por lo cual su acceso será con mecanismos de exclusión mútua. 2- El dispatcher debe ser diferente para poder explotar los múltiples procesadores. 3- Cada procesador debe tener su timer y las interrupciones internas de un proceso deben ser atendidas por el procesador donde reside. Programación Concurrente Clase 6

30 Kernel multiprocesador Cuando un procesador es interrumpido, entra al kernel e inhibe las interrupciones sobre ese procesador. Esto significa que el kernel es indivisible para ese procesador, pero podría recibir pedidos de otro procesador en el mismo tiempo. Debemos elegir entre hacer todo el kernel una sección crítica (lo cual simplifica pero hacer perder totalmente la eficiencia del multiprocesamiento) y tener múltiples secciones críticas, asociadas con los datos compartidos. En el código que sigue los datos críticos son las listas free, ready y waiting. La variable executing ahora será un arreglo con una entrada por procesador. Programación Concurrente Clase 6

31 Kernel multiprocesador El mayor cambio es el dispatcher: antes teníamos UN procesador y se suponía que siempre tenía algún proceso para ejecutar... Ahora podríamos tener momentos con procesadores desocupados (idle). Cuando un proceso es despertado o creado por un FORK debe buscar si existe un procesador idle donde ejecutarse. Esta funcionalidad puede proveerse de varios modos,nosotros elegiremos: Que cada procesador cuando esté desocupado ejecute un proceso especial que periódicamente examine la lista de READY hasta encontrar un proceso en espera. Cuando este auto-dispatcher encuentra la lista READY vacía, debe esperar hasta que aparezca un proceso en la lista y darle el control. Programación Concurrente Clase 6

32 Kernel multiprocesador process Idle { while (executing[i] == the Idle process) { while (ready list empty) Delay; lock ready list; if (ready list not empty) { remove descriptor from front of ready list; set executing[i] to point to it; } unlock ready list; } start the interval timer on processor i; load state of executing[i]; # with interrupts enabled } Programación Concurrente Clase 6

33 Kernel multiprocesador processType processDescriptor[maxProcs]; int executing[maxProcs]; # one entry per processor declarations of free, ready, and waiting lists and their locks; SVC_Handler: { # entered with interrupts inhibited on processor i save state of executing[i]; determine which primitive was invoked, then call it; } Timer_Handler: { # entered with interrupts inhibited on processor i lock ready list; insert executing[i] at end; unlock ready list; executing[ i ] = 0; dispatcher( ); } Programación Concurrente Clase 6

34 Kernel multiprocesador procedure fork(initial process state) { lock free list; remove a descriptor; unlock free list; initialize the descriptor; lock ready list; insert descriptor at end; unlock ready list; dispatcher(); } procedure quit() { lock free list; insert executing[i] at end; unlock free list; record that executing[i] has quit; executing[i] = 0; if (parent process is waiting) { lock waiting list; remove parent from that list; unlock waiting list; lock ready list; put parent on ready list; unlock ready list; } dispatcher(); } Programación Concurrente Clase 6

35 Kernel multiprocesador procedure join(name of child process) { if (child has already quit) return; lock waiting list; put executing[i] on that list; unlock waiting list; dispatcher(); } Programación Concurrente Clase 6

36 Kernel multiprocesador procedure dispatcher() { if (executing[ i ] == 0) { lock ready list; if (ready list not empty) { remove descriptor from ready list; set executing[i] to point to it; } else # ready list is empty set executing[ i ] to point to Idle process; unlock ready list; } if (executing[ i ] is not the Idle process) start timer on processor i; load state of executing[i]; # with interrupts enabled } Programación Concurrente Clase 6

37 Comentarios En la página está la evaluación del 2003, a modo de referencia. Las clases teóricas se reinician el lunes 31-5, con comunicación y sincronización por mensajes. Programación Concurrente Clase 6 El archivo de la biblioteca JAVA está disponible en la página de la cátedra.


Descargar ppt "17-5-20041 Concurrencia en JAVA JAVA es un lenguaje que tiene soporte de concurrencia, mediante Threads. Un thread es un proceso liviano (lightweight process)"

Presentaciones similares


Anuncios Google