threads
Diferencias
Muestra las diferencias entre dos versiones de la página.
| Ambos lados, revisión anteriorRevisión previaPróxima revisión | Revisión previa | ||
| threads [2014/09/24 22:33] – [Ejercicios] lmateu | threads [2018/07/06 00:04] (actual) – [Productor/consumidor] lmateu | ||
|---|---|---|---|
| Línea 115: | Línea 115: | ||
| </ | </ | ||
| - | La tipo Args se crea para poder pasar varios argumentos al thread, albergar el pid del thread | + | El tipo Args se crea para poder pasar varios argumentos al thread, albergar el pid del thread |
| y retornar el resultado final. | y retornar el resultado final. | ||
| Línea 193: | Línea 193: | ||
| Suponga que un banco atiende una infinidad de clientes que compran en el comercio con | Suponga que un banco atiende una infinidad de clientes que compran en el comercio con | ||
| la tarjeta redcompra. | la tarjeta redcompra. | ||
| - | es suficiente o la rechaze | + | es suficiente o la rechace |
| los terminales de redcompra es lento debido a la latencia de la red, de modo que | los terminales de redcompra es lento debido a la latencia de la red, de modo que | ||
| se decidió utilizar múltiples threads para comunicarse con los terminales de redcompra. | se decidió utilizar múltiples threads para comunicarse con los terminales de redcompra. | ||
| Línea 257: | Línea 257: | ||
| ==== Mutex ==== | ==== Mutex ==== | ||
| - | Los sistemas de threads | + | Los sistemas de threads |
| son los mutex que sirven específicamente para garantizar la exclusión mutua. | son los mutex que sirven específicamente para garantizar la exclusión mutua. | ||
| se manipula con las siguientes funciones: | se manipula con las siguientes funciones: | ||
| Línea 318: | Línea 318: | ||
| ==== Productor/ | ==== Productor/ | ||
| - | El siguiente programa lee de Internet un video y lo muestre | + | El siguiente programa lee de Internet un video y lo muestra |
| < | < | ||
| Línea 359: | Línea 359: | ||
| } | } | ||
| | | ||
| - | void consumidor(Buffer | + | void *consumidor(void *ptr) { // porque se usa en pthread_create |
| + | Buffer *buf= ptr; | ||
| for (;;) { | for (;;) { | ||
| Cuadro *cuadro= get(buf); | Cuadro *cuadro= get(buf); | ||
| Línea 366: | Línea 367: | ||
| mostrarCuadro(cuadro); | mostrarCuadro(cuadro); | ||
| } | } | ||
| + | return NULL; | ||
| } | } | ||
| | | ||
| void reproducirVideo() { | void reproducirVideo() { | ||
| Buffer *buf= nuevoBuffer(60); | Buffer *buf= nuevoBuffer(60); | ||
| - | | + | |
| - | pthread_create(& | + | pthread_create(& |
| productor(buf); | productor(buf); | ||
| - | pthread_join(pid, NULL); | + | pthread_join(t, NULL); |
| } | } | ||
| </ | </ | ||
| Línea 418: | Línea 420: | ||
| más energía de la necesaria y acelerando así el consumo de la batería de un notebook. | más energía de la necesaria y acelerando así el consumo de la batería de un notebook. | ||
| - | ==== Semáforos ==== | ||
| - | |||
| - | Un semáforo es un contenedor de tickets. | ||
| - | extraer un ticket con sem_wait. | ||
| - | retorna hasta que algún thread invoque sem_post para depositar un ticket. | ||
| - | |||
| - | Un semáforo se manipula con las siguientes funciones: | ||
| - | |||
| - | < | ||
| - | #include < | ||
| - | sem_t sem; | ||
| - | sem_init(& | ||
| - | sem_post(& | ||
| - | sem_wait(& | ||
| - | </ | ||
| - | |||
| - | La siguiente es una implementación correcta de la estructura Buffer del productor/ | ||
| - | |||
| - | < | ||
| - | /* Versión correcta */ | ||
| - | typedef struct { | ||
| - | int size; | ||
| - | Cuadro **array; | ||
| - | int in, out; | ||
| - | sem_t empty, full; | ||
| - | } Buffer; | ||
| - | | ||
| - | Buffer *nuevoBuffer(int size) { | ||
| - | Buffer *buf= (Buffer*)malloc(sizeof(Buffer)); | ||
| - | buf-> | ||
| - | buf-> | ||
| - | buf->in= buf-> | ||
| - | sem_init(& | ||
| - | sem_init(& | ||
| - | return buf; | ||
| - | } | ||
| - | | ||
| - | void put(Buffer *buf, Cuadro *cuadro) { | ||
| - | sem_wait(& | ||
| - | buf-> | ||
| - | buf->in= (buf-> | ||
| - | sem_post(& | ||
| - | } | ||
| - | | ||
| - | Cuadro *get(Buffer *buf) { | ||
| - | Cuadro *cuadro; | ||
| - | sem_wait(& | ||
| - | cuadro= buf-> | ||
| - | buf-> | ||
| - | sem_post(& | ||
| - | return cuadro; | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | === Ejercicio: múltiples productores y múltiples consumidores === | ||
| - | |||
| - | Hay muchos problemas que se pueden paralelizar usando la abstracción productor/ | ||
| - | En ellos la solución es la misma de arriba. | ||
| - | y los ítemes que se depositan en el Buffer. | ||
| - | El problema del productor/ | ||
| - | múltiples consumidores que comparten el mismo buffer. | ||
| - | código para depositar y extraer elementos de buffer puede sufrir de data-races. | ||
| - | Corrija el código usando un semáforo o mutex adicional. | ||
| ==== Monitores ==== | ==== Monitores ==== | ||
| Línea 774: | Línea 713: | ||
| un deadlock (a veces se traduce al español como abrazo mortal). | un deadlock (a veces se traduce al español como abrazo mortal). | ||
| - | === Primera solución | + | === Solución |
| La mejor solución a este problema es introducir un orden en los mutex y pedirlos siempre en orden | La mejor solución a este problema es introducir un orden en los mutex y pedirlos siempre en orden | ||
| Línea 806: | Línea 745: | ||
| pedir ordenadamente los mutex que protegen cada estructura. | pedir ordenadamente los mutex que protegen cada estructura. | ||
| de deadlock. | de deadlock. | ||
| - | |||
| - | === Segunda solución correcta y eficiente === | ||
| - | |||
| - | La segunda solución se basa en que el deadlock solo se produce si los 5 filósofos llegan a cenar. | ||
| - | Si se limita a 4 los filósofos que pueden cenar, ya no habrá deadlock. | ||
| - | con 4 tickets iniciales: | ||
| - | |||
| - | < | ||
| - | pthread_mutex_t palitos[5]; | ||
| - | sem_t portero; /* con 4 tickets iniciales */ | ||
| - | | ||
| - | void filosofo(int i) { | ||
| - | for (;;) { | ||
| - | sem_wait(& | ||
| - | pthread_mutex_lock(& | ||
| - | pthread_mutex_lock(& | ||
| - | comer(i, (i+1)%5); | ||
| - | pthread_mutex_unlock(& | ||
| - | pthread_mutex_unlock(& | ||
| - | sem_post(& | ||
| - | pensar(); | ||
| - | } | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | El problema de esta solución es que solo se aplica a 5 filósofos y 5 palitos. | ||
| - | generalizar a un número variable de threads accediendo a múltiples estructuras | ||
| - | de datos. | ||
| === Hambruna === | === Hambruna === | ||
| Línea 847: | Línea 758: | ||
| Para lograr el máximo paralelismo es tentador idear una solución en donde un filósofo pueda empezar | Para lograr el máximo paralelismo es tentador idear una solución en donde un filósofo pueda empezar | ||
| a comer solo cuando ambos palitos están libres, pero si alguno está ocupado, el que está libre sigue libre | a comer solo cuando ambos palitos están libres, pero si alguno está ocupado, el que está libre sigue libre | ||
| - | para que otro filósofo pueda ocuparlo. | + | para que otro filósofo pueda ocuparlo. |
| en la siguiente solución: | en la siguiente solución: | ||
| Línea 948: | Línea 859: | ||
| Las funciones enterRead, exitRead, enterWrite y exitWrite no realizan trabajo útil. | Las funciones enterRead, exitRead, enterWrite y exitWrite no realizan trabajo útil. | ||
| solo para efectos de sincronización. | solo para efectos de sincronización. | ||
| - | el diccionario, | + | el diccionario, |
| no retorna hasta que no hayan lectores o escritores dentro del diccionario. | no retorna hasta que no hayan lectores o escritores dentro del diccionario. | ||
| Entonces el problema de sincronización se reduce a programar las funciones enterRead, exitRead, | Entonces el problema de sincronización se reduce a programar las funciones enterRead, exitRead, | ||
| Línea 1003: | Línea 914: | ||
| El problema de esta solución es que los lectores se pueden concertar para entrar y | El problema de esta solución es que los lectores se pueden concertar para entrar y | ||
| salir alternadamente, | salir alternadamente, | ||
| - | así en // | + | así en // |
| - | + | que evitan | |
| - | === Segunda solución === | + | |
| - | + | ||
| - | Esta solución evita la hambruna por medio de la metáfora de la isapre. | + | |
| - | isapre los threads deben pedir un número antes de realizar su operación. | + | |
| - | solo puede entrar a realizar su operación cuando el número que aparece en un visor | + | |
| - | coincide con el número que se le asignó. | + | |
| - | se satisfacen en orden FIFO (//first in first out//), aunque las lecturas se hacen | + | |
| - | en paralelo y por lo tanto las salidas pueden ocurrir en un orden no FIFO. | + | |
| - | + | ||
| - | En esta solución los lectores no pueden concertarse para causar hambruna a un escritor | + | |
| - | porque una vez que el escritor recibe su número, ningún otro lector podrá entrar | + | |
| - | al diccionario. | + | |
| - | que salir y será el turno del escritor. | + | |
| - | + | ||
| - | < | + | |
| - | pthread_mutex_t mutex; | + | |
| - | pthread_cond_t cond; | + | |
| - | int readers= 0; | + | |
| - | int display= 0; | + | |
| - | int serial= 0; | + | |
| - | + | ||
| - | void enterRead() { | + | |
| - | int myNum; | + | |
| - | pthread_mutex_lock(& | + | |
| - | myNum= = serial++; | + | |
| - | while (myNum!=display) | + | |
| - | pthread_cond_wait(& | + | |
| - | readers++; | + | |
| - | display++; | + | |
| - | pthread_cond_broadcast(& | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | + | ||
| - | void exitRead() { | + | |
| - | pthread_mutex_lock(& | + | |
| - | readers--; | + | |
| - | if (readers==0) | + | |
| - | pthread_cond_broadcast(& | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | + | ||
| - | void enterWrite() { | + | |
| - | int myNum; | + | |
| - | pthread_mutex_lock(& | + | |
| - | myNum= serial++; | + | |
| - | while (readers> | + | |
| - | pthread_cond_wait(& | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | + | ||
| - | void exitWrite() { | + | |
| - | pthread_mutex_lock(& | + | |
| - | display++; | + | |
| - | pthread_cond_broadcast(& | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Nota: el broadcast | + | |
| - | que un lector que se encontraba esperando ahora puede entrar. | + | |
| - | + | ||
| - | // | + | |
| - | Supongamos que hay un escritor trabajando y n lectores esperando. | + | |
| - | caso se pueden requerir hasta O(n^2) cambios de thread a thread para que todos los lectores | + | |
| - | a trabajar. | + | |
| - | + | ||
| - | ==== Equivalencia entre herramientas de sincronización ==== | + | |
| - | + | ||
| - | Los semáforos y monitores (mutex + condición) son equivalentes, | + | |
| - | problema se puede resolver con una herramienta, | + | |
| - | herramienta. | + | |
| - | que no se puede implementar con las semáforos por sí solos. | + | |
| - | + | ||
| - | Probaremos | + | |
| - | a partir de un semáforo. | + | |
| - | + | ||
| - | === Semáforo implementado con monitores === | + | |
| - | + | ||
| - | < | + | |
| - | #include < | + | |
| - | + | ||
| - | typedef struct { | + | |
| - | int tickets; | + | |
| - | pthread_cond_t vacio; /* Condicion para esperar entrar al semáforo */ | + | |
| - | pthread_mutex_t mutex; | + | |
| - | } Semaphore; | + | |
| - | + | ||
| - | void sema_init(Semaphore *sema, int ini) { | + | |
| - | sema-> | + | |
| - | pthread_cond_init(& | + | |
| - | pthread_mutex_init(& | + | |
| - | } | + | |
| - | + | ||
| - | void sema_wait(Semaphore *sema) { | + | |
| - | pthread_mutex_lock(& | + | |
| - | while (sema-> | + | |
| - | pthread_cond_wait(& | + | |
| - | + | ||
| - | sema-> | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | + | ||
| - | void sema_post(Semaphore *sema) { | + | |
| - | pthread_mutex_lock(& | + | |
| - | sema-> | + | |
| - | pthread_cond_signal(& | + | |
| - | pthread_mutex_unlock(& | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Observe que se uso signal en vez de broadcast. | + | |
| - | tipo de threads esperando: threads que esperan obtener un ticket. | + | |
| - | una sola condición. | + | |
| - | que esperan extraer un ítem del buffer y los que esperan depositar un ítem. | + | |
| - | se necesitan 2 condiciones, | + | |
| - | broadcast en vez de signal. | + | |
| - | + | ||
| - | === Ejercicio === | + | |
| - | + | ||
| - | El problema de la implementación de más arriba es que un thread T que se despierta | + | |
| - | después de un sem_wait no necesariamente obtiene el ticket recién depositado | + | |
| - | con sem_post. | + | |
| - | ocurrir que llega un nuevo thread U que invoca sem_wait y le roba el mutex a T. | + | |
| - | El resultado es que U obtiene el ticket a pesar de que T lleva mucho más tiempo | + | |
| - | esperándolo. | + | |
| - | + | ||
| - | Modifique su implementación de modo que se garantice que el thread T reciba el ticket. | + | |
| - | Base su implementanción en la metáfora de la Isapre: para evitar las colas hay un | + | |
| - | distribuidor de números en la entrada y un visor que indica a quién se atiende | + | |
| - | actualmente. | + | |
| - | y espera hasta que el visor indique al menos ese número. | + | |
| - | + | ||
| - | === Monitores a partir de semáforos === | + | |
| - | + | ||
| - | La siguiente es una implementación de un verdadero monitor, el que fusiona el mutex | + | |
| - | con la condición, a la manera de los monitores de Java. | + | |
| - | + | ||
| - | < | + | |
| - | #include < | + | |
| - | #include < | + | |
| - | + | ||
| - | typedef struct node { | + | |
| - | sem_t wait; | + | |
| - | struct node *next; | + | |
| - | } Node; | + | |
| - | + | ||
| - | typedef struct { | + | |
| - | sem_t mutex; | + | |
| - | Node *head; | + | |
| - | } Monitor; | + | |
| - | + | ||
| - | void mon_init(Monitor *mon) { | + | |
| - | sem_init(& | + | |
| - | mon-> | + | |
| - | } | + | |
| - | + | ||
| - | void mon_lock(Monitor *mon) { | + | |
| - | sem_wait(& | + | |
| - | } | + | |
| - | + | ||
| - | void mon_unlock(Monitor *mon) { | + | |
| - | sem_post(& | + | |
| - | } | + | |
| - | + | ||
| - | void mon_wait(Monitor *mon) { | + | |
| - | Node node; | + | |
| - | sem_init(& | + | |
| - | node.next= mon-> | + | |
| - | mon-> | + | |
| - | sem_post(& | + | |
| - | sem_wait(& | + | |
| - | sem_destroy(& | + | |
| - | sem_wait(& | + | |
| - | } | + | |
| - | + | ||
| - | void mon_broadcast(Monitor *mon) { | + | |
| - | Node *node; | + | |
| - | for (node= mon-> | + | |
| - | sem_post(& | + | |
| - | mon-> | + | |
| - | } | + | |
| - | </ | + | |
| - | + | ||
| - | Este código no efectúa ninguna validación con respecto al buen uso de los monitores, | + | |
| - | es decir que las operaciones mon_unlock, mon_wait y mon_broadcast solo se invoquen | + | |
| - | cuando se obtuvo previamente el monitor con mon_lock. | + | |
| - | + | ||
| - | === Ejercicio === | + | |
| - | + | ||
| - | Implemente a partir de semáforos un monitor con múltiples condiciones, | + | |
| - | de los mutex y condiciones de pthreads. | + | |
| Línea 1217: | Línea 937: | ||
| * Pregunta 1 del [[http:// | * Pregunta 1 del [[http:// | ||
| * Pregunta 1 del [[http:// | * Pregunta 1 del [[http:// | ||
| + | * Pregunta 1 del [[http:// | ||
| * Pregunta 2 del [[http:// | * Pregunta 2 del [[http:// | ||
threads.1411598015.txt.gz · Última modificación: por lmateu
