La descarga está en progreso. Por favor, espere

La descarga está en progreso. Por favor, espere

Desarrollador Profesional de Juegos Programación III Unidad III Programación con Threads Bajo Linux.

Presentaciones similares


Presentación del tema: "Desarrollador Profesional de Juegos Programación III Unidad III Programación con Threads Bajo Linux."— Transcripción de la presentación:

1 Desarrollador Profesional de Juegos Programación III Unidad III Programación con Threads Bajo Linux

2 En Linux existen muchas librerías que implementan los threads ya que, la diferencia de los procesos (fork), es que forman parte del kernel del sistema. Probablemente la librería de threads más utilizada es la librería pthread que es la que vamos a estudiar. Para utilizar la librería pthread és necesario incluir el archivo pthread.h (#include ) y cuando se compile el programa linkearlo con la librería (-lpthread). Las funciónes de manejo de threads son las siguientes: Creación de un Thread int pthread_create (pthread_t *thread, pthread_attr_t *attr, void* (*start_routine)(void *), void * arg); Esta función crea un nuevo thread de ejecución que llevará la cabo la función start_routine al mismo tiempo que el thread principal. La función start_routine debe tener el siguinte prototipo: void * start_routine (void *param); El parámetro arg és la información que se le pasará como parámetro á la función start_routine. Si la rutina en el precisa de ningún argumento, se pondra en NULL. El parámetro thread és la dirección de memoria de una variable de tipo pthread_t donde se almacenará la información del nuevo thread creado. El parámetro attr contiéne los atributos que va la tener el nuevo thread (ver las funciones de manejo de atributos). Si queremos los atributos por defecto, pondremos NULL

3 Threads Bajo Linux Hacer que el thread principal espere a que terminen los threads en ejecución: int pthread_join (pthread_t th, void **pthread_return); La función pthread_join hace que la función que lo llama ( el thread principal) espere la la finalización de los threads secundarios antes de rematar, de forma que pueda liberar sus recursos. El parámetro th és el thread por el que se va a esperar que finalice. El parámetro pthread_return almacenará el valor devuelto por el thread finalizado. Finalizar la ejecución del thread actual void pthread_exit (void *retval); Esta función provoca la finalización del thread en ejecución devolviendo el valor retval. Es el equivalente la escribir return retval. 'Independizar' la ejecución del hilo del que lo creo (garantizar que el hilo libere sus recursos al finalizar) int pthread_detach (pthread_t th); Esta función hace que el thread th se ejecute en forma independiente del thread principal, es decir, que una vez finalizado, liberará sus recursos por si mismo. Thread detached con esta función en el se le podrá hacer un pthread_join para esperar por su finalización.

4 Threads Bajo Linux Manejo de los atributos del thread que se va la crear int pthread_attr_init (pthread_attr_t *attr); int pthread_attr_destroy(pthread_attr_t *attr); int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate); int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); int pthread_attr_setschedpolicy(pthread_attr_t *attr, int schedpolicy); int pthread_attr_getschedpolicy(pthread_attr_t *attr, int *schedpolicy); int pthread_attr_setschedparam(pthread_attr_t *attr, int schedparam); int pthread_attr_getschedparam(pthread_attr_t *attr, int *schedparam); int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched); int pthread_attr_getinheritsched(pthread_attr_t *attr, int *inheritsched); int pthread_attr_setscope(pthread_attr_t *attr, int scope); int pthread_attr_getscope(pthread_attr_t *attr, int *scope); Para poner los atributos la un thread, se debe pedir primero una estructura de tipo pthread_attr_t y luego pasarla como argumento la la función pthread_create, si la esta función le pasamos NULL como atributo el thread tomara los atributos por defecto.

5 La función pthread_attr_init inicializa el parámetro attr con los atributos por defecto del threads, mentras que pthread_attr_destroy elimina los atributos que se crearon. Para manipular los distintos atributos que tienen los threads se utilízan el resto de las funciónes, siendo las funciónes que comienzan por pthread_attr_set para poner atributos y las que comienzan porpthread_attr_get para saber que atributos tiene. Los atributos que se pueden manipular son: detachstate : controla si el thread se crea en un estado en el que se puede hacer un pthread_join (PTHREAD_CREATE_JOINABLE) o en un estado desenganchado (como si le hicieramos un pthread_detach) (PTHREAD_CREATE_DETACHED). El valor por defecto es PTHREAD_CREATE_JOINABLE. schedpolicy: controla la política de planificación del proceso (sheduling) vinculados al thread creado. Puede ser SCHED_OTHER (planificación normal), SCHED_RR (round robin) el SCHED_FIFO (planificación FIFO, también llamada FCFS (First Comed First Served)). El valor por defecto es SCHED_OTHER. schedparam: contiene los parámetros de la política de planificación de procesos empleada en el thread, normalmente su prioridad de ejecución. Por defecto la prioridad es 0 y unicamente tiene sentido con las políticas SCHED_FIFO y SCHED_RR. inheritsched: Indica si las políticas de planificación y sus parámetros se collen del proceso que lanza el thread (PTHREAD_INHERIT_SCHED) o de los atributos indicados (schedpolicy y schedparam) en el momento de crear el thread. (PTHREAD_EXPLICIT_SCHED). Por defecto es PTHREAD_EXPLICIT_SCHED. scope: el único valor soportado en Linux es PTHREAD_SCOPE_SYSTEM que indica que los threads competirán con el resto de los procesos del sistema para utilizar la CPU. El otro valor especificado estándar pero no soportado en Linux es PTHREAD_SCOPE_PROCESS que indica que los threads únicamente compiten con el resto de los threads del proceso en funcionamento.

6 Mutex, Semáforos y Condicion de Sincronización Supongamos que temos un programa que lanza hilos de ejecución para atender conexiónes para vender entradas para un espectáculo y que inicialmente tenemos 100 entradas. de esta forma podremos atender los clientes de dos en dos. Para vender las entradas cada hilo comprueba si quedan entradas (inicialmente tenemos 100). Si quedan, realiza la venta restándole 1 al número de entradas que quedan por vender. A primera vista este algoritmo es correcto, pero supongamos que queda 1 entrada por vender y la solicitan dos clientes al mismo tiempo. Cada thread atenderá un cliente; en primero lugar comprobarán si quedan entradas, viendo ambos threads que queda una entrada y procedendo a efectuar la venta. ¡ Como resultado se venderá una entrada de más !. Este tipo de situaciones recibe el nome de condición de competencia o condicion de carrera (racing condition), ya que varios procesos compiten por un recurso. Para solucionlo los threads proporcionan Mutex, Semáforos y Condición de Sincronización.

7 Mutex Los mutex permiten bloquear una parte del código, de forma que ningún otro hilo de ejecución pueda accederla mientras no se libere el bloqueo. de esta forma, si los threads bloquean el código que comprueba el número de entradas que quedan y lo liberan después de realizada la venta, obligamos a que primero se ejecute la parte crítica un proceso y luego la otra. Las funcións de manejo de mutex son las siguientes: pthread_mutex_t fastmutex= PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t recmutex=PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP; pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP; int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex);

8 Un mutex es un dispositivo de exclusión mutua útil para proteger estructuras de datos compartidas de modificaciónes concurrentes, y para implementar secciones críticas y monitores. Los mutex tienen dos estados posibles: unlocked (libre, no ocupado por ningún thread) o locked (ocupado por un thread). Un mutex nunca puede estar ocupado por más de un thread. Si un thread intenta ocupar un mutex ya ocupado, quedará a la espera de que el thread que tienen el mutex lo libere. La función pthread_mutex_init inicializa el objeto mutex indicado por el parámetro mutex de acuerdo con los atributos indicados en el parametro mutexattr. Si el parámetro mutexattr es NULL, se utilizarán los atributos por defecto. La implementación de Linux de los Threads (pthreads) únicamente soporta un atributo para los mutex: el tipo (Mutex Kind). Los mutex pueden ser "fast", "recursive" o "error checking", indicando cando el mutex puede ser vuelto a ocupar por un thread que lo tiene ya ocupado (recursive). el tipo error checking verifica que el thread que quita el bloqueo sea el mismo que el que lo puso; en los otros tipos un thread puede quitar el bloqueo puesto por un thread distinto. Por defecto el tipo es "fast".

9 Las variables de tipo pthread_mutex_t pueden ser inicializadas también de forma estática mediante las constantes PTHREAD_MUTEX_INITIALIZER (fast mutex), PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP (recursive mutex) y PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP (error checking mutex). La función pthread_mutex_lock ocupa el mutex indicado como parámetro. Si el mutex no está ocupado por otro thread, lo ocupa y continúa la ejecución, si el mutex ya el tienen ocupado otro thread, suspende la ejecución del thread hasta que esté disponible. Si el mutex ya está ocupado por el thread que intenta hacer un nuevo pthread_mutex_lock, el comportamento depende del tipo de mutex. Si es fast, el thread queda a la espera de que el mutex sea liberado, lo que producirá un dead lock (bloqueo mortal); si es error checking la función devolverá el código de error EDEADLK y si es recursive, el thread reocupa el mutex devolviendo el número de veces que lo tiene ocupado; para liberar el mutex será necesario hacer el mismo número de llamadas a pthread_mutex_unlock las veces que se tenga ocupado. pthread_mutex_trylock es igual que pthread_mutex_lock, pero no bloquea el thread si el mutex está ocupado por outro, si no que devuelve el código de error EBUSY.

10 pthread_mutex_unlock "libera" el mutex ocupado por el thread. Si el mutex es fast, siempre se liberará pero si es recursive decrementará la cuenta de ocupación del mutex, liberándolo si es 0. en el tipo error_check se verifica que el mutex pertence realmente el thread que intenta liberalo, y si no es así devuelve un código de error, los otros tipos permiten liberar mutex que no les pertencen, siendo esta una característica no portable y que no se debe utilizar (en realidad es un bug).bug pthread_mutex_destroy destruye un objeto mutex liberando todos los recursos que tenga ocupados. Únicamente se podrán destruir mutex que no esten ocupados por ningún thread. (Como en Linux los mutex no ocupan recursos, esta función en realidad no hace nada). int pthread_mutexattr_init (pthread_mutexattr_t *attr); int pthread_mutexattr_destroy (pthread_mutexattr_t *attr); int pthread_mutexattr_settype (pthread_mutexattr_t *attr,int kind); int pthread_mutexattr_gettype (const pthread_mutexattr_t *attr, int *kind); pthread_mutexattr_init inicializa el objeto mutex attr y inicializa con valores por defecto. pthread_mutexattr_destroy destruye un objeto que tiene atributos de un mutex. En Linux esta función no tienen ningún efecto. pthread_mutexattr_settype y pthread_mutexattr_gettype sirven para poner y para obtener el tipo de mutex. Los atributos pueden ser PTHREAD_MUTEX_FAST_NP, PTHREAD_MUTEX_RECURSIVE_NP o PTHREAD_MUTEX_ERRORCHECK_NP.

11 Semáforos Otro mecanismo que pueden utilizar los threads para proteger secciónes de código son los semáforos. Un semáforo es similar a un mutex con un contador. Las operaciónes principales son: espera a que el cuentador sea distinto de 0 y le resta 1 al contador antes de continuar sumar 1 al contador Si el contador es 0 indicará que el thread no puede acceder a la sección crítica, y si es >0, podrán acceder tantos threads como valor tenga el contador. Las funciónes de manejo de semáforos son: # include int sem_init(sem_t *sem, int pshared, unsigned int value); int sem_wait(sem_t *sem); int sem_trywait(sem_t *sem); int sem_post(sem_t *sem); int sem_getvalue(sem_t *sem,int *sval); int sem_destroy(sem_t *sem);

12 sem_init inicializa el semáforo sem, poniendo su cuenta inicial en value. el argumento pshared indica cando el semáforo es local al proceso actual (0) o compartido entre varios procesos (distinto de 0). Linux únicamente soporta semáforos locales al proceso actual (pshared debe ser igual a 0). sem_wait suspende la ejecución del thread mentras la cuenta del semáforo sea 0. Cuando la cuenta deje de ser 0, le resta 1 y continuará la execución. sem_trywait es similar a sem_wait pero en lugar de suspender la ejecución devolverá el error EAGAIN sem_post incrementará la cuenta del semáforo que se le indique. sem_getvalue devolverá la cuenta del semáforo sem en sval. sem_destroy elimina el semáforo que se le indique.

13 Condicion de Sincronización Una Condición de Sincronización permite suspender la ejecución de un thread mientras algúnos datos compartidos entre varios threads no cumplan un requisito. Las operacións básicas de las condicíóns de sincronización son: Señalizar una Condición (indicar que el requisito se cumple), y Esperar por la Condición (esperar que otro thread señalice la condición ). Una condición de sincronización siempre está asociada con un mutex para evitar la condición de competencia que se produce si un thread va a esperar por una condición justo al mismo tempo que otro thread la señaliza, sin darle tiempo de esperar por ella. Las funcións de manejo de condicion de sincronización son las siguientes: pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); int pthread_cond_destroy(pthread_cond_t *cond); pthread_cond_init inicializa la variable de condición cond usando los atributos especificados en el parámetro cond_attr, o los atributos por defecto si el parámetro es NULL.

14 La implementación en Linux de los threads no soporta atributos para las condiciones, de forma que cond_attr es siempre ignorado. las variables de tipo pthread_cond_t también pueden ser inicializadas estáticamente mediante la constante PTHREAD_COND_INITIALIZER. pthread_cond_signal hace que continúe uno de los threads que están a la espera de la variable que se le pasa como parámetro, pero no se sabe cual (puede ser cualquera de los que están en espera para esa condición). pthread_cond_broadcast hace que continúen todos los threads que estan la esperar por la condición especificada como parámetro. pthread_cond_wait desbloquea el mutex y suspende el thread a la espera de que se cumpla la condición indicada (cond). Antes de llamar a esta función es necesario bloquear el mutex llamando á la función pthread_mutex_lock. Antes de volver, esta función vuelve a bloquear el mutex de forma automática. Para evitar la condición de competencia ya comentada, el thread que señalice la condición con pthread_cond_signal debe bloquear el mutex antes. pthread_cond_timedwait trabaja del la mismo forma que pthread_cond_wait, pero su condición no se cumple en el tiempo indicado en el parámetro abstime, la función devuelve el error ETIMEDOUT. El tempo viene expresado en el formato de la función time (número de segundos dende el 1 de enero de 1970). pthread_cond_destroy libera una variable de condición y todos los recursos que pueda tener asociados. En la implementación de Linux no tienen ningún efecto. Tanto la función pthread_cond_wait como pthread_cond_timedwait son 'puntos de cancelación'.

15 Un caso práctico Consideremos dos variables compartidas x e y protegidas por el mutex mut y la condición cond que será señalizada cuando x sea mayor que y: int x,y; pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; Los threads que esperan, incluirán el siguiente código: // Bloqueo de la sección crítica pthread_mutex_lock (&mut); // En realidad bastaría un if, pero si hay varios threads... while(x<=y) { // Esperamos la señal.. pthread_cond_wait(&cond,&mut); } /* Aquí trabajaríamos con '''x''' y con '''y''' */ // los otros threads pueden continuar... pthread_mutex_unlock(&mut);

16 El thread que señaliza la condición tendría el código siguiente: críticapthread_mutex_lock(&mut); // Bloqueo de la sección /* Aquí trabajaríamos con '''x''' y con '''y''' */ // Señalo la condición. Si únicamente tuviera 1 thread en espera bastaría // pthread_cond_signal if (x>y) pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mut); // Libero el mutex //Si quisieramos que la espera tuviera un timeout de 5 segundos: struct timeval now; struct timespec timeout; int retcode; pthread_mutex_lock(&mut); gettimeofday(&now); timeout.tv_sec=now.tv_sec+5; timeout.tv_nsec=now.tv_usec*1000; retcode=0; while ((x<=y) && (retcode != ETIMEDOUT)) { retcode=pthread_cond_timedwait(&cond,&mut,&timeout); } if (retcode == ETIMEDOUT) { /*Error,se acabo el tiempo (pasaron los 5 segundos) */} else { /* Trabajar con las variables '''x''' e '''y''' */ } pthread_mutex_unlock(&mut);

17 Cancelación de Threads La cancelación es un mecanismo por el que un thread puede provocar la finalización de otro enviándole una petición de finalización. Dependendo de la forma de trabajar del otro thread, puede ignorar la petición, finalizar inmediatamente o esperar alcanzar un punto de cancelación. Si el thread atiende inmediatamente la petición termina devolviendo el código PTHREAD_CANCELED. Los puntos de cancelación son puntos de ejecución donde se comprueba si hay peticiónes de cancelación pendientes. Las siguientes funcións tienen puntos de cancelación: pthread_join, pthread_cond_wait, pthread_cond_timedwait pthread_testcancel, sem_wait, sigwait. Las funcions de cancelación de thread son las siguientes: int pthread_cancel(pthread_t thread); int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type,int *oldtype); int pthread_testcancel(void);

18 pthread_cancel envía una petición de cancelación al thread indicado como argumento. pthread_setcancelstate cambia el estado de cancelación del thread, es decir indica cuando se ignorarán las señales de cancelación y cando no. El parámetro state puede ser PTHREAD_CANCEL_ENABLE para permitir la cancelación o PTHREAD_CANCEL_DISABLE para no permitirla. Si el parámetro oldstate no es NULL, devolverá el estado de cancelación previo. pthread_setcanceltype cambia el tipo de respusta a las peticións de cancelación, y puede ser PTHREAD_CANCEL_ASYNCHRONOUS (para cancelación inmediata) o PTHREAD_CANCEL_DEFERRED (esperar al siguiente punto de cancelación). Si el parámetro oldtype no es NULL almacenará el tipo de cancelación anterior. pthread_testcancel únicamente sitúa un punto de cancelación en el lugar en que se llame. Los threads se créan por defecto con la cancelación activa con el tipo PTHREAD_CANCEL_DEFERRED

19 Envío y Gestion de Señales int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask); int pthread_kill(pthread_t thread, int signo); int sigwait(const sigset_t *set, int *sig); thread_sigmask cambia el conjunto de señales que va a respostar el thread. Si el parámetro oldmask no es NULL, almacenará el conjunto viejo de señales. Si how es SIG_SETMASK el conjunto de señales inicializa la newmask, si es SIG_BLOCK engádense las señales especificadas en newmask y si es SIG_UNBLOCK se quítan las señales especificadas en newmask del conjunto de señales los que va a respostar el thread. Los conjuntos de señales se manipulan con las funciónes: int sigemptyset(sigset_t *conjunto);//vacía el conjunto de señales indicado int sigfillset(sigset_t *conjunto);//incluye todos los señales existentes en el conjunto indicado. int sigaddset(sigset_t *conjunto, int numseñal);//engade el señal indicado en el conjunto). int sigdelset(sigset_t *conjunto, int numseñal); (elimina la señal indicad del conjunto). int sigismember(const sigset_t *conjunto, int numseñal); (indica si el señal indicado pertence el conjunto). pthread_kill envía la señal signo al thread especificado. sigwait suspende el thread hasta que recibe una señal del conjunto indicado en set, almacenando entonces la señal recibida en sig. Se ignorará cualqueir función asociada a la señal.


Descargar ppt "Desarrollador Profesional de Juegos Programación III Unidad III Programación con Threads Bajo Linux."

Presentaciones similares


Anuncios Google