260 likes | 426 Views
T5-multithreading. Indice. Proceso vs. Flujos Librerías de flujos Comunicación mediante memoria compartida Condición de carrera Sección Crítica Acceso en exclusión mutua Problemas Abrazos mortales. Procesos vs. Flujos. Hasta ahora…..
E N D
Indice • Proceso vs. Flujos • Librerías de flujos • Comunicaciónmediantememoriacompartida • Condición de carrera • SecciónCrítica • Acceso en exclusiónmutua • Problemas • Abrazosmortales
Procesos vs. Flujos • Hasta ahora….. • Una única secuencia de ejecución: Sólo 1 ProgramCounter y una pila • Concurrencia entre procesos, pero dentro de un proceso la ejecución era secuencial (una única secuencia de instrucciones) • No es posible ejecutar concurrentemente diferentes funciones dentro del mismo proceso • Aunque puedan haber partes del código independientes entre si
Ejemplo: aplicación cliente-servidor Cliente 1 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } DATOS GLOBALES Servidor { While(){ Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); } } • Monoproceso: sólo un cliente cada vez • Se desaprovecha las ventajas de la concurrencia y del paralelismo • Multiproceso: un proceso para cada cliente simultáneo que se quiera atender • Ejecución concurrente y/o paralela • Pero… Se desaprovechan recursos • Replicación innecesaria de estructuras de datos que almacenan los mismos valores, replicación del espacio lógico de memoria, mecanismos para intercambiar información,… Cliente 2 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } Cliente N { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … }
CASO : aplicación cliente-servidor Cliente 1 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } DATOS GLOBALES Servidor { While(){ INICIO_proceso Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_proceso } } Cliente 2 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } DATOS GLOBALES Servidor { While(){ INICIO_proceso Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_proceso } } DATOS GLOBALES Servidor { While(){ INICIO_proceso Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_proceso } } DATOS GLOBALES Servidor { While(){ INICIO_proceso Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_proceso } } DATOS GLOBALES Servidor { While(){ INICIO_proceso Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_proceso } } Cliente N { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … }
CASO : aplicación servidor • Alternativa: procesos multiflujo • Permitir diferentes secuencias de ejecución simultáneas asociadas al mismo proceso • ¿Qué necesitamos para describir una secuencia de ejecución? • Pila • Programcounter • Valores de los registros • El resto de características del proceso puede ser única (resto del espacio lógico, información sobre los dispositivos, gestión signals, etc)
Procesos vs. Flujos • Los recursos se siguen asignando en su mayoría a los procesos: • Espacio de direcciones • Dispositivos • Pero el SO planifica a nivel de Flujo (cada flujo necesita 1 CPU) • Los flujos de un proceso comparten todos los recursos asignados al proceso y todas las características • Y cada flujo tiene asociado: • Siguiente instrucción a ejecutar (valor del PC) • Zona de memoria para la pila • Estado de los registros • Un identificador • Proceso tradicional: un sólo flujo de ejecución
CASO : aplicación cliente-servidor Cliente 1 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } DATOS GLOBALES Servidor { While(){ INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO } } Cliente 2 { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … } INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO INICIO_FLUJO Esperar_peticion(); Preparar_respuesta(); Enviar_respuesta(); FIN_FLUJO Cliente N { .. Enviar_peticion(); Esperar_respuesta(); Procesar_respuesta(); … }
Internamente: Procesos vs. Flujos • 1 proceso con N flujos 1 PCB • N secuencias del código del proceso que se pueden ejecutar de forma concurrente • En el PCB hay espacio para guardar los contextos de los N flujos • Descripción de memoria • 1 región de código • 1 región de datos • 1 región de heap + N pilas (1 por flujo)
Internamente: Procesos vs. Flujos • Compartición de memoria • Entre procesos • Por defecto la memoria es privada para un proceso y nadie la puede acceder (hay llamadas a sistema que permiten pedir zonas de memoria compartida entre procesos) • Entre flujos • Todos los threads pueden acceder a todo el espacio lógico de la tarea a la que pertenecen • Cosas a tener en cuenta en la programación con threads • Cada thread tiene su pila propia donde el compilador reserva espacio para sus variables locales, parámetros, y control de su ejecución • Todas las pilas también son visibles por todos los flujos
Utilización de procesos multiflujos • Explotar paralelismo y concurrencia • Mejorar la modularidad de las aplicaciones • Aplicaciones intensivas en E/S • Flujos dedicados sólo a acceder a dispositivos • Aplicaciones servidores
Ventajas de usarflujos • Ventajas de usar varios flujos en lugar de varios procesos • Coste en tiempo de gestión: creación, destrucción y cambio de contexto • Aprovechamiento de recursos • Simplicidad del mecanismo de comunicación: memoria compartida
Gestión a nivel de usuario: Librerías de flujos • Los kernelsofrecen threads, pero su interfaz no es compatible (en general) como en el caso de los procesos, por eso se definió una interfaz implementada a nivel librería usuario. POSIX threads. • POSIX threads (Portable OperatingSystem Interface, definido por IEEE) • Interfaz de gestión de flujos a nivel de usuario • Creación y destrucción • Sincronización • Configuración de la planificación • El API de POSIX es muy potente, dependiendo del kernel la librería implementa toda la funcionalidad o solo parte de ella
Servicios de gestión de flujos • Creación • Procesofork() • Flujos pthread_create(outPth_id,in NULL, in function_name, in Pparam) • Identificación • Procesos: getpid() • Flujos : pthread_self() • Finalización • Procesos: exit(exit_code) • Flujos:pthread_exit(in Pthexit_code) • Sincronización fin de flujo • Procesos: waitpid(pid,ending_status, FLAGS) • Flujos:pthread_join(in thread_id, outPPexit_code) • Consultad las páginas de man para ver los tipos de datos exactos
Creación de flujos • pthread_create • Crea un nuevoflujoqueejecuta la rutinastart_routinepasándolecomoparámetroarg #include <pthread.h> intpthread_create(pthread_t *th, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); th: contendrá el identificador del thread attr: características del thread (si NULL se le asignanlascaracterísticaspordefecto) start_routine: @ de la rutinaqueejecutará el nuevoflujo. Esarutinapuederecibir un sóloargumenteo de tipo void * (nombre de la función) arg: parámetro de la rutina Devuelve un código de error o 0 si ok
Identificación del flujo • pthread_self • Obtiene el identificador del flujoque la ejecuta #include <pthread.h> intpthread_self(void); Devuelve el identificador del thread
Destrucción de flujos • pthread_exit • La ejecuta el flujo que acaba la ejecución • Se pasa como parámetro el valor de retorno del thread #include <pthread.h> intpthread_exit(void *status); status: valor de retorno del thread Devuelve un código de error o 0 si ok
Comunicaciónmediantememoriacompartida • Los flujos de un proceso pueden intercambiar información a través de la memoria que comparten • Accediendo más de uno a las mismas variables • Problema que puede aparecer: condición de carrera (racecondition) • Cuando el resultado de la ejecución depende del orden el que se alternen las instrucciones de los flujos (o procesos)
Ejemplo: race condition intprimero = 1 /* variable compartida */ /* flujo 1 */ If (primero) { primero --; tarea_1(); } else { tarea_2(); } /* flujo2 */ If (primero) { primero --; tarea_1(); } else { tarea_2(); } RESULTADO INCORRECTO La idea del programador era utilizar este booleano para que se ejecutara primerola tarea1 y luego la 2 (pero cada una solo 1 vez) Lo que no tuvo en cuenta es que estas operaciones no son atómicas!!!
Que tenemos en ensamblador??? haz_tarea: pushl %ebp movl %esp, %ebp subl $8, %esp movl primero, %eax testl %eax, %eax je .L2 movl primero, %eax subl $1, %eax movl %eax, primero calltarea1 jmp .L5 .L2: calltarea2 .L5: leave ret Esto es el if no es 1 instrucción Esto es la resta no es 1 instrucción Esto es el else Que pasa si hay un cambio de contexto después del movl del if al thread 2???
¿Qué pasaría?…eax valdrá 1 al volver!! FLUJO 2 FLUJO 1 haz_tarea: pushl %ebp movl %esp, %ebp subl $8, %esp movl primero, %eax testl %eax, %eax je .L2 movl primero, %eax subl $1, %eax movl %eax, primero calltarea1 jmp .L5 .L2: calltarea2 .L5: leave ret haz_tarea: pushl %ebp movl %esp, %ebp subl $8, %esp movl primero, %eax testl %eax, %eax je .L2 movl primero, %eax subl $1, %eax movl %eax, primero call tarea1 jmp .L5 .L2: call tarea2 .L5: leave ret Cambio! Cambio!
Regióncrítica • Región crítica • Líneas de código que contienen condiciones de carrera que pueden provocar resultados erróneos • Líneas de código que acceden a variables compartidas cuyo valor cambia durante la ejecución • Solución • Garantizar el acceso en exclusión mutua a estas regiones de código • ¿Evitar cambios de contexto?
Exclusiónmútua • Acceso en exclusión mutua: • Se garantiza que el acceso a la región crítica es secuencial • Mientras un flujo está ejecutando código de esa región ningún otro flujo lo hará (aunque haya cambios de contexto) • El programador debe: • Identificar regiones críticas de su código • Marcar inicio y fin de la región usando las herramientas del sistema • El sistema operativo ofrece llamadas a sistema para marcar inicio y fin de región crítica: • Inicio: si ningún otro flujo ha pedido acceso a la región crítica se deja que continúe accediendo ,sino se hace que el flujo espere hasta que se libere el acceso a la región crítica • Fin: se libera acceso a la región crítica y si algún flujo estaba esperando el permiso para acceder se le permite acceder
Interfaz pthreadsExc. mutua • A considerar: • Cada región crítica se identifica con una variable (global) de tipo pthread_mutex_t, por lo tanto, necesitamos 1 variable de este tipo por región. • Antes de utilizarla, hay que inicializarla, por lo tanto, antes de crear los threads es lo ideal
Ejemplo: Mutex intprimero = 1 /* variables compartida */ pthread_mutex_t rc1; // Nueva, tambiéncompartida pthread_mutex_init(& rc1,NULL); // INICIALIZAMOS LA VARIABLE, SOLO 1 VEZ ….. pthread_mutex_lock(& rc1); // BLOQUEO if (primero) { primero --; pthread_mutex_unlock (& rc1); //DESBLOQUEO tarea_1(); } else { pthread_mutex_unlock(& rc1); //DESBLOQUEO tarea_2(); }
Exclusiónmútua: consideraciones • Cosas que el programador debe tener en cuenta • Las regiones críticas deben ser lo más pequeñas posibles para maximizar la concurrencia • El acceso en exclusión mutua viene determinado por el identificador (variable) que protege el punto de entrada • No hace falta que tengan las mismas líneas de código • Si varias variables compartidas independientes puede ser conveniente protegerlas mediante variables diferentes