# Sincronización de procesos --- ## Procesos independientes vs cooperantes --- ## Problema del productor-consumidor - Es un problema recurrente en informática. - Es necesario sincronizar el proceso consumidor y el proceso productor para: - Evitar el consumo si no hay nada que consumir - Evitar superproducción, en el caso de que no haya espacio en el buffer. Note: Desacoplar actividades entre sí, actividades independientes que pueden tener requisitos temporales diferentes, por lo que admiten distintas configuraciones en cuanto al número de productores y consumidores. Se puede hablar aquí a modo de introducción de arquitecturas PUB/SUB ---- ### Código del productor ```cpp= do { ... produce un nuevo elemento (elemento_p) ... while (contador == MAX_ELEMENTOS) haz_nada(); buffer[indice_p] = elemento_p; indice_p = (indice_p + 1) % MAX_ELEMENTOS; contador = contador + 1; } while (TRUE); ``` :::spoiler ¿Qué significa `haz_nada()` Espera activa. Aún pasará algo de tiempo hasta que podamos solucionar eso. ::: ---- ### Código del consumidor ```cpp= do { while (contador == 0) haz_nada(); elemento_c = buffer[indice_c]; indice_c = (indice_c + 1) % MAX_ELEMENTOS; contador = contador - 1; ... consume el elemento (elemento_c) ... } while (TRUE); ``` :::spoiler ¿Qué sucede si ambos, productor y consumidor, hacen simultáneamente `contador + 1` y `contador - 1` respectivamente? No se sabe. Se produce una situación no determinista denomina **condición de carrera** ::: --- ## Condiciones de carrera | `contador + 1` | `contador - 1` | | ---- | --- | | `load r0, contador` | `load r0, contador` | | `add r0, 1` | `sub r0, 1` | | `store r0, contador` | `store r0, contador` | --- ## Condiciones de carrera - Deriva de la velocidad relativa entre dos procesos a la hora de operar sobre datos compartidos. - Dependiendo de esa velocidad obtenemos resultados diferentes (no determinista) - La operación de incremento o decremento no es atómica :::danger Una operación atómica es aquella que o se ejecuta por completo o no se ejecuta en absoluto. ::: - Necesitamos que partes del código se ejecuten de forma exclusiva: **secciones críticas** --- ## Sección crítica - Sección de código en la que se actualizan variables compartidas - La **exclusión mutua** es un mecanismo que asegura que solo un proceso accede a una sección crítica de forma exclusiva. --- ## Condiciones de una sección crítica - Se debe asegurar exclusión mutua - Se debe de garantizar progreso - Se debe de garantizar la espera limitada Note: - Progreso: un proceso que se encuentre fuera de la sección crítica no debe impedir a otro entrar en la sección crítica (Esto se ve bien con un ejemplo que se muestra más adelante) - Espera limitada: un proceso que quiera entrar en la sección crítica debe poder hacerlo en un tiempo finito --- ## ¿Cómo proporcionar secciones críticas? - Variables de control - Instrucciones máquina específicas (`test_and_set`, `swap`) - Primitivas del sistema operativo (semáforos) - Soluciones proporcionadas por terceros (lenguaje, servicios externos, etc.) Note: - Todas las soluciones asumen que los programas avanzan (progresan) y que no se puede asumir la velocidad relativa de unos con respecto a otros (los efectos de la planificación de CPU del SO) --- ## Variables de control #1 - Las operaciones de carga y almacenamiento son atómicas - Dos hilos $T_i$ y $T_j$ comparten la variable `turno` ```cpp= do { while (turno != i) haz_nada(); sección crítica turno = j; resto de la sección } while (TRUE); ``` - :heavy_check_mark: exclusión mutua - :negative_squared_cross_mark: progreso - :negative_squared_cross_mark: espera limitada Note: El mismo proceso no puede entrar dos veces seguidas, incluso si el otro proceso no necesita entrar. Eso viola la condición de progreso. --- ## Variables de control #2 ```cpp= flag[i] = false flag[j] = false ``` `flag` indica la intención del hilo `i` de acceder a la SC ```cpp= do { flag[i] = true; while (flag[j]) haz_nada(); sección crítica flag[i] = FALSE; resto de la sección } while (true); ``` - :negative_squared_cross_mark: exclusión mutua - :heavy_check_mark: progreso - :heavy_check_mark: espera limitada --- ## Variables de control #3 ```cpp= flag[i] = false flag[j] = false turno = i ``` ```cpp= do { flag[i] = true; turno = j; while (flag[j] && (turno == j)) haz_nada(); sección crítica flag[i] = false; resto de la sección } while (true); ``` - :heavy_check_mark: exclusión mutua - :heavy_check_mark: progreso - :heavy_check_mark: espera limitada --- ## Variables de control #3 - :heavy_check_mark: exclusión mutua - :heavy_check_mark: progreso - :heavy_check_mark: espera limitada - :negative_squared_cross_mark: difícil de extender a muchos procesos - :negative_squared_cross_mark: requiere memoria compartida - :negative_squared_cross_mark: implica espera activa --- ## Instrucciones `test_and_set` ``` test_and_set(&memoria) ``` ```cpp= int test_and_set (int *destino) { int aux; aux = *destino; *destino = TRUE; return (aux); } ``` ```cpp= do { while (test_and_set (&cerrojo)) haz_nada(); sección crítica cerrojo = false; resto de la sección } while (TRUE); ``` --- ## Instrucciones `test_and_set` - :heavy_check_mark: exclusión mutua - :heavy_check_mark: progreso - :heavy_check_mark: espera limitada - :heavy_check_mark: fácil de extender a muchos procesos - :negative_squared_cross_mark: requiere memoria compartida - :negative_squared_cross_mark: implica espera activa --- ## Semáforos - Son objetos gestionados por el SO - Es un tipo especial de variable que admite dos operaciones - `P(S)` Espera hasta que el semáforo sea positivo, y en ese momento lo decrementa. - `V(S)` Incrementa el semáforo en una unidad - Ambas operaciones `P` y `V` son atómicas Note: - Es el sistema operativo el que garantiza que las operaciones son atómicas. - Indicar que más adelante se explicará cómo es capaz el sistema operativo de garantizar ejecución atómica. --- ## Semáforos - :heavy_check_mark: exclusión mutua - :heavy_check_mark: progreso - :heavy_check_mark: espera limitada - :heavy_check_mark: fácil de extender a muchos procesos - :heavy_check_mark: no requiere memoria compartida - :heavy_check_mark: no implica espera activa. El SO bloquea al proceso si fuera necesario ---- ### Productor-Consumidor con semáforos - El consumidor se bloquea si no hay nada que consumir - El productor se bloquea si no hay sitio en el buffer - Se debe de poder garantizar exclusión mutua ``` smf_llenos <- 0 ; controla el número de elementos en el buffer smf_vacios <- N; controla el número de huecos en el buffer mutex <- 1; proporciona acceso en exclusión mútua ``` ---- ### Productor-Consumidor con semáforos #### Código del productor ```cpp= dato = producir() P(smf_vacios) P(exmut) insertar(dato) V(exmut) V(smf_llenos) ``` #### Código del consumidor ```cpp= P(smf_llenos) P(exmut) dato = extraer() V(exmut) V(smf_vacios) ``` ---- ### Productor-Consumidor con semáforos - ¿Importa el orden de las operaciones `P`y `V`? - ¿Cómo sería si hubiera dos productores o dos consumidores? - Si algún participante tuviera que esperar, ¿lo haría de forma activa? ###### tags: `SOG` `Sincronización`
{"metaMigratedAt":"2023-06-15T12:59:34.876Z","metaMigratedFrom":"YAML","title":"Sincronización de Procesos","breaks":true,"slideOptions":"{\"transition\":\"slide\",\"theme\":\"solarized\"}","contributors":"[{\"id\":\"93855254-5bcf-438a-b2d1-f9f805e036e4\",\"add\":8645,\"del\":1091}]"}
    418 views