1 / 79

Comunicación y sincronización entre procesos

Comunicación y sincronización entre procesos. María de los Santos Pérez Hernández mperez@fi.upm.es. Índice. Procesos concurrentes. El problema de la sección crítica. Problemas clásicos de comunicación y sincronización. Mecanismos de comunicación y sincronización. Interbloqueos.

nemo
Download Presentation

Comunicación y sincronización entre procesos

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Comunicación y sincronización entre procesos María de los Santos Pérez Hernández mperez@fi.upm.es

  2. Índice • Procesos concurrentes. • El problema de la sección crítica. • Problemas clásicos de comunicación y sincronización. • Mecanismos de comunicación ysincronización. • Interbloqueos. mperez@fi.upm.es

  3. Referencias bibliográficas • “Sistemas Operativos: una visión aplicada” Jesús Carretero et al. McGraw Hill, 2001 • “Sistemas Operativos” Willian Stalling Willian Stalling Prentice Hall, 1997 • “Operating System Concepts” A. Silberschatz, P. Galvin Addison-Wesley, 1998 mperez@fi.upm.es

  4. Procesos concurrentes (I) • Modelos • Multiprogramación en un único procesador • Multiprocesador • Multicomputador (proceso distribuido) • Razones • Compartir recursos físicos • Compartir recursos lógicos • Acelerar los cálculos • Modularidad • Comodidad mperez@fi.upm.es

  5. Procesos concurrentes (II) • Tipos de procesos • Independientes • Cooperantes • Interacción entre procesos • Compiten por recursos • Comparten recursos mperez@fi.upm.es

  6. Calcula la suma de los N primeros números utilizando procesos ligeros. int suma_total = 0; void suma_parcial(int ni, int nf) { int j = 0; int suma_parcial = 0; for (j = ni; j <= nf; j++) suma_parcial =suma_parcial +j; suma_total = suma_total + suma_parcial; pthread_exit(0); } Si varios procesos ejecutanconcurrentemente este código se puedeobtener un resultado incorrecto. Solución: secciones críticas Problema de la interacción entre procesos (procesos ligeros) mperez@fi.upm.es

  7. void suma_parcial(int ni, int nf) { int j = 0; int suma_parcial = 0; for (j = ni; j <= nf; j++) suma_parcial = suma_parcial + j; Entrada en la sección crítica suma_total = suma_total+ suma_parcial; Salida de la sección crítica pthread_exit(0); } Requisitos para ofrecer secciones críticas: Exclusión mutua Progreso Espera limitada Ejemplo con sección crítica(procesos ligeros) mperez@fi.upm.es

  8. void ingresar(char *cuenta, int cantidad) { int saldo, fd; fd = open(cuenta, O_RDWR); read(fd, &saldo,sizeof(int)); saldo = saldo + cantidad; lseek(fd, 0, SEEK_SET); write(fd, &saldo,sizeof(int)); close(fd); return; } Si dos procesos ejecutan concurrentemente este código se puedeperder algún ingreso. Solución:secciones críticas Problema de la interacción entre procesos mperez@fi.upm.es

  9. void ingresar(char *cuenta, int cantidad) { int saldo, fd; fd = open(cuenta, O_RDWR); Entrada en la sección críticaread(fd, &saldo, sizeof(int)); saldo = saldo + cantidad; lseek(fd, 0, SEEK_SET); write(fd, &saldo, sizeof(int)); Salida de la sección crítica close(fd); return; } Requisitos para ofrecer secciones críticas: Exclusión mutua Progreso Espera limitada Ejemplo con sección crítica mperez@fi.upm.es

  10. Mecanismos de comunicación • Ficheros • Pipes • FIFOS • Variables en memoria compartida • Paso de mensajes • Sockets mperez@fi.upm.es

  11. Mecanismos de Sincronización • Construcciones de los lenguajes concurrentes (procesos ligeros) • Servicios del sistema operativo: • Señales (asincronismo) • Pipes • FIFOS • Semáforos • Mutex y variables condicionales • Paso de mensajes • Las operaciones de sincronización deben seratómicas mperez@fi.upm.es

  12. Identificación Mecanismos de nombrado Sin nombre Con nombre local Con nombre de red Identificador interno Identificador propio Descriptor de fichero Flujo de datos Unidireccional Bidireccional Buffering Sin buffering Con buffering Sincronización Síncrono (bloqueante) Asíncrono (no bloqueante) Características de los mecanismos de comunicación mperez@fi.upm.es

  13. Problemas clásicos de comunicación y sincronización • Productor-consumidor: • Lectores-escritores: mperez@fi.upm.es

  14. Problemas clásicos de comunicación y sincronización (II) • Comunicación cliente-servidor: Computadora Computadora Petición Proceso Proceso cliente servidor S.O. Respuesta mperez@fi.upm.es

  15. Mecanismo de comunicación y sincronización sin nombre Sólo puede utilizarse entre los procesos hijos del procesoque creó el pipe int pipe(int fildes[2]); Identificación: dos descriptores de fichero Flujo de datos: unidireccional Con buffering read(fildes[0],buffer,n) Pipe vacío se bloquea el lector Pipe con p bytes Si p n devuelve n Si p n devuelve p Si pipe vacío y no hay escritores devuelve 0 write(fildes[1], buffer, n) Pipe lleno se bloquea el escritor Si no hay lectores se recibe la señal SIGPIPE Lecturas y escrituras atómicas (cuidado con tamaños grandes) Pipes mperez@fi.upm.es

  16. void main(void) { int fildes[2]; /* pipesincronización */ char c; /* caráctersincronización */ pipe(fildes); write(fildes[1], &c, 1); /* necesario para entrar en la seccion crítica la primera vez */ if (fork() == 0) { /* proceso hijo */ for(;;) { read(fildes[0], &c, 1); /* entrada sección crítica */ /* sección crítica */ write(fildes[1], &c, 1); /* salida seccion crítica */ } } else /* proceso padre */ { for(;;) { read(fildes[0], &c, 1); /* entrada sección crítica*/ /* sección crítica */ write(fildes[1], &c, 1); /* salida sección crítica */ } } } Secciones críticas con pipes mperez@fi.upm.es

  17. void main(void) { int fildes[2]; /* pipe para comunicar y sincronizar */ int dato_p[4]; /* datos a producir */ int dato_c; /* dato a consumir */ pipe(fildes); if (fork() == 0) /* productor */ { for(;;) { /* producir dato_p */ write(fildes[1],dato_p, 4*sizeof(int)); } } else /* consumidor */ { for(;;) { read(fildes[0], &dato_c, sizeof(int)); /* consumir dato*/ } } } read write Proceso Proceso hijo padre SO pipe Flujo de datos Productor-consumidor con pipes mperez@fi.upm.es

  18. /* programa que ejecuta el mandato “ls | wc” */ void main(void) { int fd[2]; pid_t pid; if (pipe(fd) < 0) { perror(``pipe''); exit(1); } pid = fork(); switch(pid) { case -1: /* error */ perror(``fork''); exit(1); case 0: /* proceso hijo ejecuta “ls” */ close(fd[0]); /* cierra el pipe de lectura */ close(STDOUT_FILENO); /* cierra la salida estandar */ dup(fd[1]); close(fd[1]); execlp(``ls'',``ls'',NULL); perror(``execlp''); exit(1); default: /* proceso padre ejecuta “wc” */ close(fd[1]); /* cierra el pipe de escritura */ close(STDIN_FILENO); /* cierra la entrada estandar */ dup(fd[0]); close(fd[0]); execlp(``wc'',``wc'',NULL); perror(``execlp''); } } Ejecución de mandatos con pipes mperez@fi.upm.es

  19. Ejecución de mandatos con pipes (II) mperez@fi.upm.es

  20. Igual que los pipes Mecanismo de comunicación y sincronización con nombre Misma máquina Servicios mkfifo(char *name,mode_tmode); Crea un FIFO con nombre name open(char *name, int flag); Abre un FIFO (para lectura, escritura o ambas) Bloquea hasta que haya algún proceso en el otro extremo Lectura y escritura mediante read() y write() Igual semántica que los pipes Cierre de un FIFO mediante close() Borrado de un FIFOmediante unlink() FIFOS mperez@fi.upm.es

  21. Mecanismo de sincronización Misma máquina Objeto con un valor entero Dos operaciones atómicas s_espera(s) { s = s - 1; if (s < 0) Bloquear al proceso } s_abre(s) { s = s + 1; if (s <= 0) Desbloquear a un proceso bloqueado } Secciones críticas con semáforos: s_espera(s); /* entrada en la seccion critica */ /* seccion critica */ s_abre(s); /* salida de la seccion critica */ El semáforo debe tener valor inicial1 Semáforos mperez@fi.upm.es

  22. sem_init(sem_t *sem, int shared, int val); Inicializa un semáforo sin nombre int sem_destroy(sem_t *sem); Destruye un semáforo sin nombre sem_t *sem_open(char *name,int flag,mode_t mode,intval); Abre (crea) un semáforo con nombre. int sem_close(sem_t *sem); Cierra un semáforo con nombre. int sem_unlink(char *name); Borra un semáforo con nombre. int sem_wait(sem_t *sem); Realiza la operación s_espera sobre un semáforo. int sem_post(sem_t *sem); Realiza la operación s_abre sobre un semáforo. Semáforos POSIX mperez@fi.upm.es

  23. Productor-consumidor con semáforos(buffer acotado y circular) mperez@fi.upm.es

  24. #define MAX_BUFFER 1024 #define DATOS_A_PRODUCIR 100000 sem_t elementos; /* elementos buffer */ sem_t huecos; /* huecos buffer */ sem_t mutex; /* controla excl.mutua */ int buffer[MAX_BUFFER];/*buffer común*/ void main(void) { pthread_t th1, th2; /* id.threads*/ /* inicializar los semaforos */ sem_init(&elementos, 0, 0); sem_init(&huecos, 0, MAX_BUFFER); sem_init(&mutex, 0, 1); /* crear los procesos ligeros */ pthread_create(&th1, NULL, Productor, NULL); pthread_create(&th2, NULL, Consumidor, NULL); /* esperar su finalizacion */ pthread_join(th1, NULL); pthread_join(th2, NULL); sem_destroy(&huecos); sem_destroy(&elementos); sem_destroy(&mutex); exit(0); } Productor-consumidor con semáforos (II) mperez@fi.upm.es

  25. Productor-consumidor con semáforos (III) void Productor(void) /* código del productor */ { int pos = 0; /* posición dentro del buffer */ int dato; /* dato a producir */ int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { dato = i; /* producir dato */ sem_wait(&huecos); /* un hueco menos */ sem_wait(&mutex); /* entra en la sección crítica */ buffer[pos] = dato; pos = (pos + 1) % MAX_BUFFER; sem_post(&mutex); /* deja la sección crítica */ sem_post(&elementos); /* un elemento más */ } pthread_exit(0); } mperez@fi.upm.es

  26. Productor-consumidor con semáforos (IV) void Consumidor(void) /* código del Consumidor */ { int pos = 0; int dato; int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { sem_wait(&elementos); /* un elemento menos */ sem_wait(&mutex); /* entra en la sección crítica */ dato = buffer[pos]; pos = (pos + 1) % MAX_BUFFER; sem_post(&mutex); /* deja la sección crítica */ sem_post(&huecos); /* un hueco más */ /* consumir dato */ } pthread_exit(0); } mperez@fi.upm.es

  27. int dato = 5; /* recurso */ int n_lectores = 0;/* num. Lectores*/ sem_t sem_lec; /* acceso n_lectores */ sem_t mutex; /*acceso a dato */ void main(void) { pthread_t th1, th2, th3, th4; sem_init(&mutex, 0, 1); sem_init(&sem_lec, 0, 1); pthread_create(&th1, NULL, Lector, NULL); pthread_create(&th2, NULL, Escritor, NULL); pthread_create(&th3, NULL, Lector, NULL); pthread_create(&th4, NULL, Escritor, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); pthread_join(th3, NULL); pthread_join(th4, NULL); /*cerrar todos los semaforos*/ sem_destroy(&mutex); sem_destroy(&sem_lec); exit(0); } Lectores-escritores con semáforos mperez@fi.upm.es

  28. Lectores-escritores con semáforos (II) void Lector(void) /* código del lector */ { sem_wait(&sem_lec); n_lectores = n_lectores + 1; if (n_lectores == 1) sem_wait(&mutex); sem_post(&sem_lec); printf(``%d\n'', dato); /* leer dato */ sem_wait(&sem_lec); n_lectores = n_lectores - 1; if (n_lectores == 0) sem_post(&mutex); sem_post(&sem_lec); pthread_exit(0); } mperez@fi.upm.es

  29. Lectores-escritores con semáforos (III) void Escritor(void) /* código del escritor */ { sem_wait(&mutex); dato = dato + 2; /* modificar el recurso */ sem_post(&mutex); pthread_exit(0); } mperez@fi.upm.es

  30. Permiten crear un segmento de memoria compartido entre procesos dela misma máquina. Declaración independiente de variables Objetos de memoria compartida mperez@fi.upm.es

  31. int shm_open(char *name, int oflag, mode_t mode); Crea un objeto de memoria a compartir entre procesos int shm_open (char *name, int oflag); Sólo apertura int close(int fd); Cierre. El objeto persiste hasta que es cerrado por el último proceso. int shm_unlink(const char *name); Borra una zona de memoria compartida. void *mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off); Establece una proyección entre el espacio de direcciones de un proceso y un descriptor de fichero u objeto de memoria compartida. Servicios mperez@fi.upm.es

  32. Servicios (II) • len especifica el número de bytes a proyectar. • prot el tipo de acceso (lectura, escritura o ejecución). • PROT_READ, PROT_WRITE, PROT_EXEC o PROT_NONE. • flags especifica información sobre elmanejo de los datos proyectados (compartidos, privado, etc.). • MAP_SHARED, MAP_PRIVATE, ... • fildes representa el descriptor de fichero delfichero o descriptor del objeto de memoria a proyectar. • off desplazamiento dentro del fichero apartir de cual se realiza la proyección. • void munmap(void *addr, size_t len); • Desproyecta parte del espacio de direcciones de unproceso comenzando en la dirección addr. El tamaño de la región a desproyectar es len. mperez@fi.upm.es

  33. Productor-consumidor con objetos de memoria compartida • Productor: • Crea la zona de memoria compartida (shm_open) • Le asigna espacio (ftruncate) • int ftruncate (int fd, size_t len); • Proyecta la zona de memoria compartida en su espacio de direcciones (mmap) • Utiliza la zona de memoria compartida • Desproyecta la zona de memoria compartida • Cierra y borra el objeto de memoria compartida mperez@fi.upm.es

  34. Productor-consumidor con objetos de memoria compartida (II) • Consumidor: • Debe esperar a que la zona de memoria compartida estecreada • Abre la zona de memoria compartida (shm_open) • Proyecta la zona de memoria compartida en su espaciode direcciones (mmap) • Utiliza la zona de memoria compartida • Cierra el objeto de memoria compartida mperez@fi.upm.es

  35. Código del productor #define MAX_BUFFER 1024 /* tamaño del buffer */ #define DATOS_A_PRODUCIR 100000 /* datos a producir */ sem_t *elementos; /* elementos en el buffer */ sem_t *huecos; /* huecos en el buffer */ sem_t *mutex; /* acceso al buffer */ void main(int argc, char *argv[]) { int shd; int *buffer; /* buffer común */ /* el productor crea el segmento de memoria compartida */ shd = shm_open("BUFFER", O_CREAT|O_WRONLY, 0700); ftruncate(shd, MAX_BUFFER * sizeof(int)); /* proyectar el objeto de memoria compartida en el espacio de direcciones del productor */ mperez@fi.upm.es

  36. Código del productor (II) buffer = (int *) mmap(NULL, MAX_BUFFER * sizeof(int), PROT_WRITE, MAP_SHARED, shd, 0); /* El productor crea los semáforos */ elementos = sem_open("ELEMENTOS", O_CREAT, 0700, 0); huecos = sem_open("HUECOS", O_CREAT, 0700, MAX_BUFFER); mutex = sem_open("MUTEX", O_CREAT, 0700, 1); Productor(buffer); /* desproyectar el buffer compartido */ munmap(buffer, MAX_BUFFER * sizeof(int)); close(shd); /* cerrar el objeto de memoria compartida */ shm_unlink("BUFFER"); /* borrar el objeto de memoria */ sem_close(elementos); sem_close(huecos); sem_close(mutex); sem_unlink("ELEMENTOS"); sem_unlink("HUECOS"); sem_unlink("MUTEX"); } mperez@fi.upm.es

  37. Código del productor (III) void Productor(int *buffer){ /* código del productor */ int pos = 0; /* posición dentro del buffer */ int dato; /* dato a producir */ int i; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { dato = i; /* producir dato */ sem_wait(huecos); /* un hueco menos */ sem_wait(mutex); buffer[pos] = dato; pos = (pos + 1) % MAX_BUFFER; sem_post(mutex); sem_post(elementos); /* un elemento más */ } return; } mperez@fi.upm.es

  38. Código del consumidor #define MAX_BUFFER 1024 /* tamaño del buffer */ #define DATOS_A_PRODUCIR 100000 /* datos a producir */ sem_t *elementos; /* elementos en el buffer */ sem_t *huecos; /* huecos en el buffer */ sem_t *mutex; /* acceso al buffer */ void main(int argc, char *argv[]) { int shd; int *buffer; /* buffer común */ /* el consumidor abre el segmento de memoria compartida */ shd = shm_open("BUFFER", O_RDONLY); /* proyectar el objeto de memoria compartida en el espacio de direcciones del productor */ mperez@fi.upm.es

  39. Código del consumidor (II) buffer = (int *) mmap(NULL, MAX_BUFFER * sizeof(int), PROT_READ, MAP_SHARED, shd, 0); /* El consumidor abre los semáforos */ elementos = sem_open("ELEMENTOS", 0); huecos = sem_open("HUECOS", 0); mutex = sem_open("MUTEX", 0); Consumidor(buffer); /* desproyectar el buffer compartido */ munmap(buffer, MAX_BUFFER * sizeof(int)); close(shd); /* cerrar el objeto de memoria compartida */ /* cerrar los semáforos */ sem_close(elementos); sem_close(huecos); sem_close(mutex); } mperez@fi.upm.es

  40. Código del consumidor (III) void Consumidor(char *buffer) /*código delConsumidor*/ { int pos = 0; int i, dato; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { sem_wait(elementos); /* un elemento menos */ sem_wait(mutex); dato = buffer[pos]; pos = (pos + 1) % MAX_BUFFER; sem_post(mutex); sem_post(huecos); /* un hueco mas */ printf("Consume %d \n", dato); /* consumir dato */ } return; } mperez@fi.upm.es

  41. Mutex y variables condicionales • Un mutex es un mecanismo de sincronización indicado para procesosligeros. • Es un semáforo binario con dos operaciones atómicas: • lock(m): Intenta bloquear el mutex; si el mutex ya está bloqueadoel proceso se suspende. • unlock(m): Desbloquea el mutex; si existen procesos bloqueadosen el mutex se desbloquea a uno. mperez@fi.upm.es

  42. Secciones críticas con mutex lock(m); /* entrada en la sección crítica */ /* sección crítica */ unlock(s); /* salida de la sección crítica */ • La operación unlock debe realizarla el proceso ligeroque ejecutólock mperez@fi.upm.es

  43. Variables condicionales • Variables de sincronización asociadas a un mutex • Conveniente ejecutarlas entre lock y unlock. • Dos operaciones atómicas: • wait: Bloquea al proceso ligero que la ejecuta yle expulsa del mutex • signal: Desbloquea a uno o varios procesos suspendidos enla variable condicional. El proceso que se despierta compitede nuevo por el mutex mperez@fi.upm.es

  44. Proceso ligero A: lock(mutex); /* acceso al recurso */ /* codigo de la sección crítica */ while (condicion == FALSE) wait(condition, mutex); /* resto de la sección crítica */ unlock(mutex); Proceso ligero B lock(mutex); /* acceso al recurso */ /* código de la sección crítica */ /* se modifica la condicción y ésta se hace TRUE */ condicion = true; signal(condition, mutex); unlock(mutex); Importante utilizar while Uso de mutex y variables condicionales mperez@fi.upm.es

  45. Servicios • int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) • Inicializa un mutex. • int pthread_mutex_destroy(pthread_mutex_t *mutex); • Destruye un mutex. • int pthread_mutex_lock(pthread_mutex_t *mutex); • Intenta obtener el mutex. Bloquea al proceso ligero siel mutex se encuentra adquirido por otro proceso ligero. • int pthread_mutex_unlock(pthread_mutex_t *mutex); • Desbloquea el mutex. • int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr); • Inicializa una variable condicional. • int pthread_cond_destroy(pthread_cond_t *cond); • Destruye una variable condicional. mperez@fi.upm.es

  46. Servicios (II) • int pthread_cond_signal(pthread_cond_t *cond); • Se reactivan uno o más de los procesos ligeros queestán suspendidos en la variable condicional cond. • No tiene efecto si no hay ningún proceso ligero esperando(diferente a los semáforos). • int pthread_cond_broadcast(pthread_cond_t *cond); • Todos los threads suspendidos en la variable condicionalcondse reactivan. • No tiene efecto si no hay ningún proceso ligero esperando. • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); • Suspende al proceso ligero hasta que otro procesoseñaliza la variable condicional cond. • Automáticamente se libera el mutex. Cuando sedespierta el proceso ligero vuelve a competir por el mutex. mperez@fi.upm.es

  47. Productor-consumidor con mutex #define MAX_BUFFER 1024 /* tamaño del buffer */ #define DATOS_A_PRODUCIR 100000 /* datos a producir */ pthread_mutex_t mutex; /* mutex para controlar el acceso al buffercompartido */ pthread_cond_t no_lleno; /* llenado del buffer */ pthread_cond_t no_vacio; /* vaciado del buffer */ int n_elementos; /* elementos en el buffer */ int buffer[MAX_BUFFER]; /* buffer común */ main(int argc, char *argv[]) { pthread_t th1, th2; pthread_mutex_init(&mutex, NULL); mperez@fi.upm.es

  48. Productor-consumidor con mutex (II) pthread_cond_init(&no_lleno, NULL); pthread_cond_init(&no_vacio, NULL); pthread_create(&th1, NULL, Productor, NULL); pthread_create(&th2, NULL, Consumidor, NULL); pthread_join(th1, NULL); pthread_join(th2, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&no_lleno); pthread_cond_destroy(&no_vacio); exit(0); } mperez@fi.upm.es

  49. Productor-consumidor con mutex (III) void Productor(void) { /* código del productor */ int dato, i ,pos = 0; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { dato = i; /* producir dato */ pthread_mutex_lock(&mutex); /* acceder al buffer */ while (n_elementos==MAX_BUFFER)/* si buffer lleno */ pthread_cond_wait(&no_lleno, &mutex);/* bloqueo */ buffer[pos] = dato; pos = (pos + 1) % MAX_BUFFER; n_elementos ++; pthread_cond_signal(&no_vacio);/* buffer no vacío */ pthread_mutex_unlock(&mutex); } pthread_exit(0); } mperez@fi.upm.es

  50. Productor-consumidor con mutex (IV) void Consumidor(void) { /* código del consumidor */ int dato, i ,pos = 0; for(i=0; i < DATOS_A_PRODUCIR; i++ ) { pthread_mutex_lock(&mutex); /* acceder al buffer */ while (n_elementos == 0) /* si buffer vacío */ pthread_cond_wait(&no_vacio, &mutex);/* bloqueo */ dato = buffer[pos]; pos = (pos + 1) % MAX_BUFFER; n_elementos --; pthread_cond_signal(&no_lleno);/* buffer no lleno */ pthread_mutex_unlock(&mutex); printf("Consume %d \n", dato); /* consume dato */ } pthread_exit(0); } mperez@fi.upm.es

More Related