Vous êtes sur la page 1sur 11

INGENIER IA TECNICA en INFORMATICA de SISTEMAS CONCURRENTE ASIGNATURA: PROGRAMACION CODIGO ASIGNATURA: 403182/533012 MATERIAL AUXILIAR: NINGUNO 2 horas DURACION:

ON: Fecha 25 de Enero de 2005 CONTACTO : programacion.concurrente@lsi.uned.es

TEORIA

Equivalencia de herramientas. Implementar las primitivas de los sem aforos a partir de las regiones cr ticas condicionales. (2.5pt)

var c : entero; resource s : c;

wait(s) : region s when c > 0 do c := c - 1; signal(s) : region s do c := c + 1; init(s,v) : region s do c := v;

Explicar cuales son las ventajas e inconvenientes de los sucesos en relaci on con las regiones cr ticas condicionales. (1.5pt) Entre las ventajas de los sucesos respecto a las RCC gura la posibilidad de denir varias colas de eventos (frente a s olo una por recurso), a gusto del programador y realizar una gesti on expl cita de dichas colas, lo que, bien

empleado, redunda en una mayor eciencia, puesto que se pueden seleccionar los procesos que queremos despertar con una granularidad denida por el programador, mientras que con las RCC, cuando un proceso abandona la RC, se despierta a todos los que estaban bloqueados en la cola de eventos para que re-eval uen la condici on, con la probable p erdida de eciencia que ello conlleva en t erminos de cambios de contexto. Para que se produzca una mejora apreciable de la eciencia es necesario que exista un alto grado de procesos y de condiciones distintas a evaluar dentro de las regiones cr ticas, de lo contrario esta ventaja puede convertirse en una desventaja. Entre las desventajas de los sucesos respecto a las RCC, hay que decir que la gesti on expl cita de las colas puede introducir una sobrecarga perjudicial para la eciencia cuando hay muchas colas y pocos procesos, o bien cuando hay pocas condiciones distintas a considerar. Por u ltimo, pero no menos importante, hay que destacar que las RCC son estupendas para reejar condiciones de sincronizaci on, pero los sucesos lo son mucho menos porque despu es de hacer un CAUSE, un proceso sigue en ejecuci on, lo que hace que para cuando un proceso salga de la cola del evento, es posible que la condici on que necesitaba haya dejado de cumplirse, lo que obliga a encerrar los AWAIT en estructuras del estilo while not Condici on do AWAIT(ev); o a hacer un dise no muy cuidadoso de las modicaciones de las condiciones dentro de las regiones cr ticas. EJERCICIO

Un banco tiene repartidos diversos cajeros autom aticos repartidos por la ciudad. Las operaciones que se pueden hacer desde un cajero son consultar el saldo y sacar dinero. Queremos simular el comportamiento de los cajeros de la siguiente manera. Escribir un programa en pseudoc odigo en el que varios procesos cajero vayan realizando aleatoriamente estas operaciones. Es decir, dentro de un bucle innito, un proceso cajero tiene que decidir aleatoriamente si realiza una consulta o una disposici on de efectivo, decidir aleatoriamente el n umero de cuenta y, en caso de tratarse de una disposici on de efectivo, la cantidad. El grado de concurrencia entre los procesos debe ser alto. Varios cajeros pueden querer acceder o modicar los datos de una cuenta al mismo tiempo. Habr a que asegurar la consistencia en los datos. Adem as, los cajeros

denegar an las disposiciones de efectivo si dentro de las u ltimas 24 horas se han retirado m as de 500 euros. Para poder seguir lo que est a ocurriendo es menester que los procesos emitan mensajes por pantalla de vez en cuando para indicar lo que est a pasando, por ejemplo: Cajero xxx: Quiero realizar una disposici on de 124 euros de la cuenta 27, Cajero yyy: Operaci on no permitida, l mite de disposici on alcanzado etc... (6pt). Se trata de SIMULAR, como bien dice el enunciado, por lo que una soluci on basada en memoria compartida es perfectamente aplicable. Es un problema de libro de lectores y escritores. Muchos lectores pueden leer concurrentemente de una misma cuenta, pero s olo uno puede escribir. En el enunciado se indica claramente que la concurrencia es muy importante. Imaginemos una red bancaria donde cuando un usuario introduce una tarjeta en un cajero, todos los dem as quedan bloqueados. Se trata de una situaci on absolutamente inaceptable. Una situaci on mejor ser a aquella en la que cuando se introduce una tarjeta, se queda bloqueada s olo la cuenta correspondiente. A un as , no es una soluci on del todo correcta. Podemos pensar en tarjetas de empresa, que tienen multitud de tarjetas f sicas asociadas a la misma cuenta, o a tarjetas familiares, situaci on bastante habitual. El proceso para realizar correctamente la pregunta deber a incluir ciertos pasos. En primer lugar debemos decidir nuestra estructura de procesos y paso de informaci on. Aqu optaremos por un u nico tipo de proceo cajero, del cual habr a muchas materalizaciones independientes. En cuanto a la informaci on, utilizaremos una memoria compartida con los datos de las cuentas. Ser a recomendable establecer expl citamente cu ales son los requerimientos de concurrencia y las condiciones de sincronizaci on entre estos procesos cajero. Requerimientos de concurrencia. 1. Varios procesos cajero pueden querer realizar consultas al mismo tiempo sobre la memor a compartida. Esto debe ser permitido. 2. M as concretamente, se debe permitir que varios procesos cajero consulten la misma cuenta de forma concurrente. 3. Las consultas a los datos DEBEN hacerse en zonas concurrentes. (e.d. no en exclusi on mutua).

Condiciones de sincronizaci on 1. Si hay un proceso modicando los datos de una cuenta no puede haber ni lectores ni otros escritores activos sobre dicha cuenta. 2. Puede haber cajeros modicando concurrentemente las cuentas siempre y cuando cada uno modique una cuenta distinta. Otros condicionamientos previos al correcto desarrollo del programa en pseudoc odigo ser an: 1. Deben existir las estructuras de datos adecuadas (en este caso una tabla con las cuentas, que deben incluir el saldo y una lista con las u ltimas extracciones y su fecha y hora). 2. Deben dise narse mensajes informativos apropiados. Este u ltimo punto es fundamental. Puesto que se trata de llevar a cabo una simulaci on, podemos imaginar el proceso visto desde un observador como una especie de caja negra, de la que lo u nico que sale son los mensajes informativos. Si los procesos no explican con claridad lo que est an haciendo, la simulaci on resulta decepcionante; no se puede apreciar con claridad la concurrencia, ni tampoco las condiciones de sincronizaci on. Despu es de estas consideraciones, podemos concretar un poco m as. Se trata del problema cl asico de los lectores y escritores. Esto no deber a resultar muy sorprendente puesto que en programaci on concurrente hay tres problemas cl asicos: el problema de los lectores y escritores, el problema de los productores y consumidores y el problema de los l osofos (cuyo valor es m as pedag ogico que pr actico). De modo que tenemos dos problemas cl asicos y este es uno de ellos. En el libro de texto de la asignatura (Palma et al.) podemos encontrar el problema de los lectores y escritores resuelto con: sem aforos, regiones cr ticas condicionales, monitores, buzones, canales y mediante invocaci on remota. No hay pues falta de alternativas. El u nico renamiento a tener en cuenta para este problema es que se trata de un problema de lectores y escritores generalizado, puesto que hay uno por cada cuenta.

Los sem aforos son una herramienta de bajo nivel y poco recomendable para problemas complejos, sin embargo, al tratarse de un problema ampliamente estudiado es perfectamente aceptable el utilizarlos. Adem as, se trata de una soluci on que escala muy bien a tener vectores de sem aforos, lo que podr a ser complicado con otras herramientas como los monitores. Hechas todas estas precisiones, podemos pasar escribir el pseudoc odigo correspondiente. Hemos escogido la soluci on con prioridad para los escritores, puesto que lo m as habitual es que los cajeros se utilicen para sacar dinero con m as frecuencia que para consultar. En lugar de dar la soluci on en pseudoc odigo, vamos a dar un programa escrito en C con ayuda de la librer a POSIX Threads.

// Compilado en SuSE Linux 9.2 con // gcc -O3 -o programa_cajeros programa_cajeros.c -lpthread #include <stdlib.h> #include <pthread.h> #define #define #define #define #define #define MAXCAJEROS 500 MAXCUENTAS 10000 MAXDISP 500 TOPE 1000000 TIEMPO_LIMITE 10 ESPERA_ALEATORIA 20

enum t_operacion {consulta, disposicion}; typedef struct { int tiempo, cantidad; } t_extracciones; typedef struct { t_extracciones entrada; struct lista* siguiente; } lista; typedef struct { int saldo; lista* extracciones;

entrada_cuenta;

entrada_cuenta cuentas[MAXCUENTAS]; int nl[MAXCUENTAS], nle[MAXCUENTAS], nee[MAXCUENTAS]; pthread_t hilos[MAXCAJEROS]; pthread_mutex_t mutex[MAXCUENTAS], lector[MAXCUENTAS], escritor[MAXCUENTAS]; int escribiendo[MAXCUENTAS]; int i,j; int protocolo_entrada_lectura(int no_cuenta, int no_cajero) { printf("Cajero %3d : Empiezo a leer de la cuenta %d\n", no_cajero, no_cuenta); pthread_mutex_lock (&mutex[no_cuenta]); // Si se esta escribiendo o existen escritores en espera // el lector debe ser bloqueado if (escribiendo[no_cuenta] > 0 || nee[no_cuenta] > 0) {

nle[no_cuenta] ++; pthread_mutex_unlock (&mutex[no_cuenta]); pthread_mutex_lock (&lector[no_cuenta]); nle[no_cuenta]--; } nl[no_cuenta]++; if (nle[no_cuenta] > 0) {// Desbloqueo encadenado pthread_mutex_unlock (&lector[no_cuenta]); } else { pthread_mutex_unlock (&mutex[no_cuenta]); } return 0; } int protocolo_salida_lectura(int no_cuenta, int no_cajero) { pthread_mutex_lock (&mutex[no_cuenta]);

nl[no_cuenta]--; // Desbloquear un escritor si es posible if (nl[no_cuenta] == 0 && nee[no_cuenta] > 0) { pthread_mutex_unlock(&escritor[no_cuenta]); } else { pthread_mutex_unlock(&mutex[no_cuenta]); } printf("Cajero %3d : Termino de leer de la cuenta %d\n", no_cajero, no_cuenta); } int protocolo_entrada_escritura(int no_cuenta, int no_cajero) { printf("Cajero %3d : Empiezo a escribir en la cuenta %d\n", no_cajero, no_cuenta); pthread_mutex_lock(&mutex[no_cuenta]); // Si se esta escribiendo o existen lectores // el escritor debe ser bloqueado if (nl[no_cuenta] > 0 || escribiendo[no_cuenta] > 0) { nee[no_cuenta]++; pthread_mutex_unlock(&mutex[no_cuenta]); pthread_mutex_lock(&escritor[no_cuenta]); nee[no_cuenta] --; } escribiendo[no_cuenta] = 1; pthread_mutex_unlock(&mutex[no_cuenta]); } int protocolo_salida_escritura(int no_cuenta, int no_cajero) { pthread_mutex_lock(&mutex[no_cuenta]); // Esto no viene en el libro escribiendo[no_cuenta] = 0; // En el libro pone ne := ne -1 // Desbloquear un escritor que este a la espera // Y si no hay, desbloquear a un lector que este a la espera

if (nee[no_cuenta] > 0) { pthread_mutex_unlock(&escritor[no_cuenta]); } else if (nle[no_cuenta] > 0) { pthread_mutex_unlock(&lector[no_cuenta]); } else { pthread_mutex_unlock(&mutex[no_cuenta]); } printf("Cajero %3d : Termino de escribir en la cuenta %d\n", no_cajero, no_cuenta); } void * proceso_cajero(void *p) { int id = (int) p; enum t_operacion operacion; unsigned cantidad, cuenta, fechayhora; int total24h = 0; lista* recorre = NULL; t_extracciones miextraccion; lista* ext = NULL; lista* aux = NULL; int ahora = 0; int espera = 0;

for(;;) { total24h = 0; cantidad = 0; // Elegir numero de cuenta y operacion cuenta = (int) (MAXCUENTAS * (float) rand() / (RAND_MAX +1.0)); operacion = (int) (2 * (float) rand() / (RAND_MAX + 1.0)); if (operacion == disposicion) {

cantidad = (int) (TOPE * (float) rand() / (RAND_MAX + 1.0)) + 1; printf("Cajero %3d : Quiero disponer %d euros de la cuenta %d\n", id, cantidad, cuenta); protocolo_entrada_escritura(cuenta, id); ahora = time(NULL); for(ext = cuentas[cuenta].extracciones ; ext != NULL && (ext->entrada.tiempo - ahora < TIEMPO_LIMITE); ext = ext->siguiente) { total24h += ext->entrada.cantidad; } for(; ext != NULL; ext = ext->siguiente) { aux = ext->siguiente; free(aux); }

if (cuentas[cuenta].saldo < cantidad) printf("Cajero %3d : Saldo insuficiente. Saldo actual %d, importe %d\n", id, cuentas[cuenta].saldo, cantidad); else if (total24h > MAXDISP) printf("Cajero %3d : Se ha superado el l mite de disposicion para el periodo de tiempo\n", id); else { cuentas[cuenta].saldo -= cantidad; printf("Cajero %3d : Operaci on realizada correctamente. Saldo resultante %d\n", id, cuentas[cuenta].saldo); miextraccion.tiempo = time(NULL); miextraccion.cantidad = cantidad; lista* p = (lista *) malloc(sizeof(lista)); p->entrada = miextraccion; p->siguiente = cuentas[cuenta].extracciones; cuentas[cuenta].extracciones = p; };

protocolo_salida_escritura(cuenta, id); espera = (int) (ESPERA_ALEATORIA * (float) rand() / (RAND_MAX +1.0)); sleep(espera); } else if (operacion == consulta) { printf("Cajero %3d : Quiero realizar una consulta de saldo sobre la cuenta %d\n", id, cuenta); protocolo_entrada_lectura(cuenta, id); printf("Cajero %3d : El saldo de la cuenta %d asciende a %d euros\n", id, cuenta, cuentas[cuenta].saldo); protocolo_salida_lectura(cuenta, id); espera = (int) (ESPERA_ALEATORIA * (float) rand() / (RAND_MAX +1.0)); sleep(espera); } else { printf("Operaci on desconocida\n"); exit(-1); } } } int main(int argc, char* argv[]) { int rc; printf("Principio de la ejecucion del programa\n"); for(i = 0; i < MAXCUENTAS; i++) { cuentas[i].saldo = (int) (TOPE * (float) rand() / (RAND_MAX +1.0)); cuentas[i].extracciones = NULL; printf("El saldo inicial de la cuenta %d es de %d euros\n", i, cuentas[i].saldo); } for(j = 0; j < MAXCAJEROS; j++) { printf("Creando cajero %d\n", j); rc = pthread_create(&hilos[j], NULL, proceso_cajero, (void *)j); if (rc) {

printf("ERROR; el codigo de salida de pthread_create() es %d\n", rc); exit(-1); } rc = pthread_mutex_init (&mutex[j], NULL); if (rc) { printf("ERROR; el codigo de salida de pthread_mutex_init() es %d\n", rc); exit(-1); } } pthread_join(hilos[0], NULL); };

Vous aimerez peut-être aussi