310 likes | 434 Views
Sincronización Bajo Nivel. Cecilia Hernández 2007-1. Múltiples proceesos/hebras en un sistema Seguro?. No . Errores si un proceso/hebra escribe estado que podría escribir/leer otro proceso/hebra Resultado: No se puede predecir, normalmente difícil de reproducir. nueva. vi. g++. previa.
E N D
SincronizaciónBajo Nivel Cecilia Hernández 2007-1
Múltiples proceesos/hebras en un sistemaSeguro? • No. Errores si un proceso/hebra escribe estado que podría escribir/leer otro proceso/hebra • Resultado: No se puede predecir, normalmente difícil de reproducir nueva vi g++ previa h1 suma h3 h2
Procesos/hebras aislados/no aislados • Procesos/hebras aisladas o independientes: Procesos/hebras no comparten datos entre ellos • Resultados de ejecución de procesos/hebras no se ven afectados por la planificación • Determinístico : Mismas entradas -> mismos resultados • Ejemplos Multiplicación de matrices, Web server respondiendo requerimientos para diversos clientes • Procesos/hebras no aisladas: Comparten estado • Resultados pueden verse afectados por planifiación • No determínistico : Mismas entradas -> resultados distintos • Muy difícil de depurar (encontrar errores)
Por qué compartir? • Costo • Comprar M, amortizar costo permitiendo compartir a N entidades (N>M) • Ejemplos: Una impresora muchos archivos, un computador con muchos procesos, una carretera con muchos autos • Información • Un proceso puede necesitar de los resultados de otros • Proporciona velocidad: hebras ejecutándose concurrentemente/paralelamente • Proporciona modularidad: capacidad de compartir estado permite separar tareas y comunicarlas sólo cuando es necesario • Compartir información y recursos muy importante en sociedad moderna. Impresoras, Telefono, internet, etc)
Ejemplo condición de carrera //variable global int suma = 0; void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { suma++; } } hebra1 hebra2 lw $t0, offset($s0) tiempo lw $t0, offset($s0) addi $t0, $t0, 1 sw $t0, offset($s0) addi $t0, $t0, 1 sw $t0, offset($s0)
Qué hacer? • Nada: Puede estar bien. Si suma pierde actualizaciones no importa. • En la mayoría de las aplicaciones si importa en algún contexto • No compartir: duplicar estado • Tratar de maximizar este criterio • No siempre posible • Hay una solución general? Si • Origen del problema? … Entrelazado en la ejecución de hebras • Entonces, solución prevenirlo
lw $t0, offset($s0) addi $t0, $t0, 1 sw $t0, offset($s0) lw $t0, offset($s0) addi $t0, $t0, 1 sw $t0, offset($s0) Atomicidad: controlando condiciones de carrera • Unidad atómica : secuencia de instrucciones garantizada en su ejecución atómica, como si fuera sólo una • Si dos hebras ejecutan la misma unidad atómica al mismo tiemp, una hebra ejecutará la secuencia completa antes que la otra comience hebra1 hebra2 tiempo
Requerimientos de Secciones Críticas • Exclusión Mutua • A lo más una hebra/proceso puede estar en la sección crítica • Progreso • Si una hebra/proceso no está en sección crítica, entonces hebra/proceso no puede impedir que otra hebra/proceso ingrese a sección crítica • Espera finita • Si una hebra/proceso está esperando entrar a la sección crítica, entonces en algún momento debe entrar (no esperar infinitamente) • Eficiencia • El overhead de entrar/salir de la sección crítica debe ser pequeño en comparación con el tiempo que toma la sección crítica
Soporte de HW para conseguir atomicidad • Idea es prevenir que sólo una hebra a la vez ejecute sección crítica • Cuál es sección crítica en ejemplo? • HW podría proporcionar instrucción que permitiera incremento atómico • Aplicable a nuestro ejemplo, pero no general • En realidad HW proporciona instrucciones atómicas que permiten construir primitivas atómicas aplicables a cualquier requerimiento • Solución General: locks (bloqueos) • Justo antes de entrar a sección crítica hebra obtiene lock y antes de salir lo libera lock Sección crítica unlock
Primitiva de sincronización : Locks • Lock: variable compartida con 2 operaciones • lock : obtiene lock, si está usado espera. • unlock : libera lock, si hay alguien esperando por el puede obtenerlo • Cómo se usa? Al identificar sección crítica en código se utiliza lock y unlock al principio y fin de sección • Nuestro ejemplo usando locks sería Resultado: Sólo una hebra ejecutando suma++ a la vez Acceso mutuamente exclusivo locks referidos como mutex en este contexto Ahora sección crítica es atómica lock_t suma_lock=INIT; void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { lock(suma_lock); suma++; unlock(suma_lock); } }
Implementando locks (1) lock_t L = 1; lock( L ){ while( L == 0); L = 0; } unlock( L ){ L = 1; } Funciona?
Implementando locks (2) • Para sistema con un procesador lock( L ){ desabilitat_int(); while( L == 0); L = 0; habilita_int(); } unlock( L ){ L = 1; } Funciona? Qué pasa si lock está tomado?
Implementando locks en multiprocesadores • Desabilitando interrupciones en todos los procesadores? • Muy caro • HW provee instrucciones que permiten implementar locks sin interferir con las interrupciones • Instrucciones atómicas proporcionadas por HW • Test and Set • Atomic swap (aswap) en Intel instrucción xchg • http://www.intel.com/cd/ids/developer/asmo-na/eng/dc/threading/333935.htm (implementando locks escalables en Multicores) • Ambas se utilizan para implementar locks
Instrucción Test and Set int test_and_set(int &target){ int rv = target; target = 1; return rv; } • Atómicamente verifica si la celda de memoria es cero, si es así la setea en 1. Si es 1 no hace nada. Retorna el valor antiguo 0 1 1 1 target = 0 target = 1 Retorna rv = 0 target = 1 target = 1 Retorna rv = 1
Implementando locks con TAS (Test and Set) void lock(int &lock) { while(test_and_set(lock)); } void unlock(int &lock){ lock = 0; } Recorde nuestro ejemplo lock_t suma_lock=INIT; void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { lock(suma_lock); suma++; unlock(suma_lock); } }
Swap atómico (aswap) void aswap(int &a, int &b){ int temp = a; a = b; b = temp; } Intercambio es atómico 0 1 1 0 a = 0 B = 1 a = 1 b = 0
Implementando locks con aswap lock = 0; //global void lock(int &lock){ int key = 1; while(key == 1)aswap(lock, key); } void unlock(int &lock){ lock = 0; } Recorde nuestro ejemplo lock_t suma_lock=INIT; void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { lock(suma_lock); suma++; unlock(suma_lock); } } Qué tienen en común ambas Implementaciones?
SC1 P3 P2 P1 SC2 P3 P4 SC3 Múltiples secciones críticasprotegida con locks • Múltiples hebras en ejecución pueden tener múltiples secciones críticas • Cada sección crítica debe estar protegida con su propio lock • Se usan locks para garantizar exclusión mutua
Spinlocks • Hebras spin (se dan vuelta en loop) hasta que obtienen lock • Implementaciones previas usando TAS y aswap son de este tipo • Problemas con spinning? • Utilizan CPU para verificar estado sin poder progresar en ejecución • Por qué podría ser bueno? • Por qué podría se malo? • Alguna alternativa?
Spinlocks en un procesador • Por cuánto tiempo la hebra que espera lock usa CPU? • Hasta que consigue lock, para ello cada vez que esta en CPU consume su quantum esperando • Normalmente en este sistema en lugar se spin se bloquea • Locks implementan colas de espera cuando lock es liberado se entrega lock a la primera hebra en la cola de espera de lock
Spin o bloqueo en multiprocesador • Hebras pueden ejecutarse en multiples CPU • Bloqueo no es gratis • Requiere cambio de estado (bloquear y resumir) de hebras lo que requiere a SO manejo de sus estructuras de datos • Decisión debería considerarse cuando se sabe cuanto tiempo se necesita hasta la liberación de lock • Si se libera pronto Spinlock es mejor • Si no mejor bloqueo • Algoritmo • Spin por el largo del costo del bloqueo • Si lock no esta disponible, bloqueo Hebra en ejecución Espera lock Costo bloqueo tiempo spin bloqueo Lock liberado por otra hebra Esta hebra obtiene lock
Desempeño • Suponga que costo de bloqueo es N ciclos • Bloquear y resumir hebra • Si hebra obtiene lock después de M ciclos de espera ocupada (spinning) (M <= N) entonces M es el costo óptimo • Alternativa de bloqueo inmediato habría sido al costo N, pero spinning conseguimos M (y M <= N) • Pero caso • Si hebra espera por N ciclos spinning y luego se bloquea y resume porque lock fue liberado • costo bloqueo es 2N (un N para spinning y N para bloqueo y resumen de hebra) • Desempeño siempre dentro de factor de 2 del óptimo
Ejemplo exclusión mutua usando locks mutex pthreads void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { suma++; } } Condición de carrera suma++ (3 instrucciones de lenguaje de máquina) Sección crítica suma++ Sincronización Usando locks (hacer atómica las tres instrucciones que componen suma++)
Mutex pthreads • pthread_mutex_t suma_lock; • declara mutex • pthread_mutex_lock(&suma_lock) • Operación lock sobre variable suma_ lock • pthread_mutex_unlock(&suma_lock); • Operación unlock sobre variable suma_lock • suma_lock = PTHREAD_MUTEX_INITIALIZER;
Ejemplo (1) pthread_mutex_t suma_lock; void *uno(void *p) { int *pi = (int *)p; pthread_mutex_lock(&suma_lock); for (int i = 0; i < *pi; i++) { suma++; } pthread_mutex_unlock(&suma_lock); } main(){ suma_lock = PTHREAD_MUTEX_INITIALIZER; }
Ejemplo(2) pthread_mutex_t suma_lock; void *uno(void *p) { int *pi = (int *)p; for (int i = 0; i < *pi; i++) { pthread_mutex_lock(&suma_lock); suma++; pthread_mutex_unlock(&suma_lock); } } Main(){ suma_lock = PTHREAD_MUTEX_INITIALIZER; }
Locks read/write • Utilizados por aplicaciones que permiten mayor concurrencia • El requerimiento de exclusión mutua es para evitar problemas de lectura/escritura • Hebras que leen y hebras que escriben no pueden inteferir en sus operaciones • Múltiples hebras pueden estar leyendo simultaneamente SC SC W1 W0 R1 R0 R1 R0 W0 Sólo una hebra de escritura puede accesar recurso Múltiples hebras lectoras pueden accesar recurso
Requerimientos soportados por locks • Exclusión Mutua • A lo más una hebra a la vez está en sección crítica • Progreso (no deadlock) • Si hay requerimientos concurrentes debe permitir el progreso de uno. • No depende del hebras que están fuera de sección crítica • Espera finita (no starvation) • Hebra intentando entrar en sección crítica eventualmente tendrá éxito • NOTA • Estos requerimientos no necesariamente se cumplen si no se utilizan adecuadamente • Requerimientos pueden verse afectados si programación de aplicación con hebras no incluyen pares lock/unlock adecuadamente
Resumen • Múltiples hebras + compartiendo estado/recursos = condiciones de carrera • Como solucionarlo? • Estado privado no necesita sincronización • Problemas de condición de carrera difíciles de reproducir y descubrir • Locks es una alternativa de bajo nivel para incorporar sincronización y evitar condiciones de carrera • Spin locks, bloqueo • Se requiere hacerlo bien para respetar requerimientos de transparencia anterior
Mecanismos de Sincronización para Secciones Críticas • Locks • Elementales, usados para construir otros • Semáforos • Básicos, a veces difíciles de manejar correctamente • Monitores • De alto nivel, debe ser soportado por lenguaje de programación • Fácil de usarlos • En Java es posible crear monitores con synchronized() • Mensajes • Modelo de comunicación y sincronización basado en tx atómica de datos a través de un canal