Concurrencia y sincronización — ejercicios tipo examen con condiciones de carrera, semáforos paso a paso y la leche de siempre (y un Peterson que no te deja entrar)
En el primer simulacro de este bloque me encontré una pregunta de test-and-set y contesté sin pensar que «devuelve true si consigue entrar». Era justo al revés: devuelve el valor antiguo, y true significa que alguien está dentro. Desde entonces, en concurrencia leo cada definición dos veces antes de marcar nada.
Este artículo es para eso: practicar con preguntas como las del examen hasta que las definiciones salgan solas. Si aún no has leído la teoría, empieza por concurrencia y sincronización: aquí ya no se explica desde cero, aunque cada pregunta trae su explicación. Y si quieres ver antes las trazas resueltas paso a paso, tienes la práctica guiada de concurrencia.
Tabla de Contenidos
Cómo usar este artículo
- Haz el test sin mirar la teoría. Cada pregunta tiene su botón «Comprobar»: así ves la explicación justo cuando la necesitas.
- Las múltiples penalizan, como en el examen: cada opción falsa marcada resta. Si dudas de una, piensa si de verdad la marcarías en el examen.
- Las trazas se hacen con papel. Antes de rellenar las casillas, apunta en una hoja el valor del semáforo y el estado de cada proceso paso a paso.
- Las de desarrollo, escríbelas enteras y compáralas después con la respuesta modelo y con la lista de lo que se valora. Al final tienes el chuletario para repasar.
Test: concurrencia, sección crítica y sincronización
Treinta preguntas con todos los formatos del examen: única, múltiple que penaliza, verdadero o falso, huecos, relacionar, ordenar y respuesta corta. Cubren el tema entero, desde la definición de concurrencia hasta los filósofos.
Tu portátil tiene un solo núcleo y tienes abiertos onedrive, el campus y spotify. ¿Cuál de estas frases es correcta?
- Los tres procesos son concurrentes aunque nunca se ejecuten dos instrucciones suyas en el mismo instante
- No hay concurrencia porque solo hay un núcleo
- Hay paralelismo, porque los tres avanzan a la vez
- Solo hay concurrencia si los tres procesos comparten datos
Solución
Respuesta: Los tres procesos son concurrentes aunque nunca se ejecuten dos instrucciones suyas en el mismo instante
Concurrencia es la existencia simultánea de varios procesos en ejecución. No implica ejecución simultánea: con un núcleo, los procesos se intercalan.
Concurrencia, paralelismo y computación distribuida. Indica si es verdadero o falso:
- El paralelismo es un caso particular de la concurrencia.
- Si dos procesos son concurrentes, se están ejecutando en dos procesadores distintos.
- Para que haya paralelismo hacen falta varios procesadores (o núcleos).
- La computación distribuida es concurrencia en un único procesador muy rápido.
Solución
- V — El paralelismo es un caso particular de la concurrencia.
- F — Si dos procesos son concurrentes, se están ejecutando en dos procesadores distintos.
- V — Para que haya paralelismo hacen falta varios procesadores (o núcleos).
- F — La computación distribuida es concurrencia en un único procesador muy rápido.
Todo paralelismo es concurrencia, pero no al revés. Paralelismo = ejecución simultánea en varios procesadores; si esos procesadores están en distintas máquinas unidas por una red, hablamos de computación distribuida.
Completa con la palabra que falta:
Cuando varios procesos existen a la vez hablamos de ____. Si además se ejecutan a la vez en varios procesadores, es ____. Y si esos procesadores están repartidos por una ____, la computación es ____.
Palabras:
- red
- multiprogramación
- concurrencia
- distribuida
- caché
- secuencial
- paralelismo
Solución
Cuando varios procesos existen a la vez hablamos de concurrencia. Si además se ejecutan a la vez en varios procesadores, es paralelismo. Y si esos procesadores están repartidos por una red, la computación es distribuida.
De más general a más concreto: concurrencia → paralelismo → computación distribuida (paralelismo en red).
¿Con qué mecanismos puede un programador expresar la concurrencia en su código?
- Llamando a una API de hilos, como pthreads
- Con objetos concurrentes que ofrece el lenguaje
- Con sentencias concurrentes del propio lenguaje
- Declarando las variables como globales
- Aumentando el tamaño de la caché
- Guardando los datos en un fichero compartido
Solución
Respuesta: Llamando a una API de hilos, como pthreads · Con objetos concurrentes que ofrece el lenguaje · Con sentencias concurrentes del propio lenguaje
Tres maneras: una API (pthreads), objetos concurrentes (Java, C++) y sentencias concurrentes (Ada, Go…).
¿Para qué sirve la programación concurrente?
- Para describir de forma natural actividades del mundo real que ocurren a la vez
- Para marcar trozos de código que pueden ir en paralelo y aprovechar varios procesadores
- Para escribir con más facilidad ciertos algoritmos
- Para que el programa dé siempre el mismo resultado en cada ejecución
- Para no tener que proteger los datos compartidos
- Para tener más dispositivos de entrada y salida
Solución
Respuesta: Para describir de forma natural actividades del mundo real que ocurren a la vez · Para marcar trozos de código que pueden ir en paralelo y aprovechar varios procesadores · Para escribir con más facilidad ciertos algoritmos
Expresar mejor el mundo real, aprovechar varios procesadores y simplificar algunos algoritmos. El precio: resultados que pueden depender del intercalado.
¿Por qué un fallo de concurrencia es tan difícil de reproducir al depurar?
- Porque el resultado depende de cómo se intercalen las instrucciones de los hilos, y ese intercalado cambia de una ejecución a otra
- Porque el compilador elimina las variables compartidas
- Porque los hilos no se pueden depurar con un depurador
- Porque solo ocurre en ordenadores con un núcleo
Solución
Respuesta: Porque el resultado depende de cómo se intercalen las instrucciones de los hilos, y ese intercalado cambia de una ejecución a otra
Un programa concurrente tiene que funcionar con todos los intercalados posibles; probarlo diez veces y que salga bien no demuestra nada.
¿Cómo se llama la situación en la que el resultado de un programa depende del orden en que los hilos acceden a un dato compartido?
Solución
Respuesta: Condición de carrera
Es una condición de carrera: dos hilos «corren» hacia el mismo dato y gana quien escribe el último.
visitas vale 10 y dos hilos ejecutan visitas = visitas + 1 (leer, sumar, escribir). El planificador hace: lee H1, lee H2, suma H1, suma H2, escribe H1, escribe H2. ¿Cuánto vale visitas al final?
- 11
- 12
- 10
- Depende del número de núcleos
Solución
Respuesta: 11
H1: 10 → 11. H2 también leyó 10 antes de que H1 escribiera: 10 → 11. El segundo escribe pisa el 11 con otro 11. Se pierde una actualización.
Ordena lo que hace la CPU para ejecutar contador = contador + 1:
- Guardar el registro en la posición de memoria de contador
- Cargar el valor de contador desde la memoria a un registro
- Sumar 1 al registro
Solución
Orden correcto:
- Cargar el valor de contador desde la memoria a un registro
- Sumar 1 al registro
- Guardar el registro en la posición de memoria de contador
Leer (lw), sumar (addi) y escribir (sw). Si el planificador expulsa al hilo entre el primer y el último paso, otro hilo puede leer un valor viejo.
¿Qué es una sección crítica?
- El trozo de código de un proceso o hilo donde accede a datos que comparte con otros
- La parte del sistema operativo que se ejecuta en modo núcleo
- Cualquier bucle infinito de un programa
- El código que más tiempo de CPU consume
Solución
Respuesta: El trozo de código de un proceso o hilo donde accede a datos que comparte con otros
Sección crítica: segmento de código donde se accede a datos compartidos. Requisito básico: exclusión mutua, nunca más de un proceso o hilo dentro.
¿Qué tres condiciones debe cumplir una solución al problema de la sección crítica?
- Exclusión mutua
- Progreso
- Espera limitada
- Espera activa
- Prioridad del proceso más corto
- Interbloqueo
Solución
Respuesta: Exclusión mutua · Progreso · Espera limitada
Exclusión mutua (uno dentro como mucho), progreso (si está libre, se decide pronto y solo entre los que quieren entrar) y espera limitada (nadie se cuela delante de ti sin límite).
Relaciona cada requisito con su significado:
| Exclusión mutua | |
| Progreso | |
| Espera limitada |
Definiciones:
- Si nadie está dentro y alguien quiere entrar, la decisión la toman solo los interesados y en un tiempo finito
- Hay un máximo de veces que otros pueden entrar antes que un hilo que ya lo ha pedido
- Nunca hay más de un hilo dentro de la sección crítica
- El hilo que entra primero sale el último
Solución
- Exclusión mutua → Nunca hay más de un hilo dentro de la sección crítica
- Progreso → Si nadie está dentro y alguien quiere entrar, la decisión la toman solo los interesados y en un tiempo finito
- Espera limitada → Hay un máximo de veces que otros pueden entrar antes que un hilo que ya lo ha pedido
La exclusión mutua protege el dato; el progreso y la espera limitada evitan que alguien se quede esperando para siempre.
Proteger la sección crítica inhibiendo las interrupciones. Verdadero o falso:
- Mientras un proceso está en su sección crítica, ningún otro proceso del sistema puede avanzar en esa CPU.
- Funciona igual de bien en un multiprocesador.
- Garantiza la espera limitada.
- Impide que el temporizador expulse al proceso mientras toca el dato compartido.
Solución
- V — Mientras un proceso está en su sección crítica, ningún otro proceso del sistema puede avanzar en esa CPU.
- F — Funciona igual de bien en un multiprocesador.
- F — Garantiza la espera limitada.
- V — Impide que el temporizador expulse al proceso mientras toca el dato compartido.
Sin interrupciones no hay expulsión, así que en un solo procesador hay exclusión mutua. Pero congela toda la concurrencia, no sirve con varios procesadores (cada uno tiene sus interrupciones) y no garantiza espera limitada.
Relaciona cada instrucción o idea con su descripción:
| Instrucción atómica | |
| Test-and-set | |
| Compare-and-swap | |
| LOCK BTS |
Definiciones:
- Pone la variable a verdadero y devuelve el valor que tenía antes
- Si la variable vale lo esperado, le pone un valor nuevo y devuelve verdadero
- Versión de x86: la CPU se queda el bus mientras hace el test-and-set
- Se ejecuta entera sin que nada la interrumpa
- Desactiva las interrupciones de todos los procesadores a la vez
Solución
- Instrucción atómica → Se ejecuta entera sin que nada la interrumpa
- Test-and-set → Pone la variable a verdadero y devuelve el valor que tenía antes
- Compare-and-swap → Si la variable vale lo esperado, le pone un valor nuevo y devuelve verdadero
- LOCK BTS → Versión de x86: la CPU se queda el bus mientras hace el test-and-set
Las tres son ayudas del hardware: leen y modifican una celda de memoria en un solo paso indivisible. IBM ya tenía en los años 60 un SWAP atómico; MIPS y ARM usan la pareja LL/SC.
Un hilo ejecuta while (test_and_set(ocupado)) {} y en ese momento ocupado vale true. ¿Qué pasa?
- La llamada devuelve true, ocupado sigue a true y el hilo da otra vuelta esperando
- La llamada devuelve false y el hilo entra en la sección crítica
- La llamada pone ocupado a false para que entre el siguiente
- El hilo se bloquea y deja libre la CPU
Solución
Respuesta: La llamada devuelve true, ocupado sigue a true y el hilo da otra vuelta esperando
test-and-set devuelve el valor antiguo. Si era true, alguien está dentro y el bucle sigue girando (espera activa). Solo entra quien recibe false.
Un semáforo vale 0 y hay dos procesos bloqueados en su cola. Otro proceso hace signal. ¿Qué ocurre, con la definición de los apuntes?
- Se despierta uno de los dos bloqueados y el semáforo sigue valiendo 0
- El semáforo pasa a valer 1 y los dos siguen bloqueados
- Se despiertan los dos y el semáforo vale 1
- El proceso que hace signal se bloquea
Solución
Respuesta: Se despierta uno de los dos bloqueados y el semáforo sigue valiendo 0
signal despierta a un proceso si hay alguno esperando; solo si la cola está vacía suma 1. Por eso el valor no sube cuando alguien estaba dormido.
En el piso, cada compañero hace: si no hay leche y no hay nota, deja una nota, compra y quita la nota. ¿Qué puede salir mal?
- Que los dos miren, vean que no hay nota y compren los dos, porque el cambio de contexto llega antes de dejar la nota
- Que nadie compre nunca
- Que uno compre dos veces seguidas
- Nada: la nota lo resuelve siempre
Solución
Respuesta: Que los dos miren, vean que no hay nota y compren los dos, porque el cambio de contexto llega antes de dejar la nota
Mirar la nota y dejarla son dos pasos separados. Si los dos miran antes de que ninguno la deje, compran los dos: la «solución» funciona casi siempre y falla en el peor momento.
Ahora cada uno deja SU nota (notaA, notaB) antes de mirar la del otro. ¿Cuál es el peor caso?
- Que los dos dejen su nota, cada uno vea la del otro y ninguno compre
- Que compren los dos
- Que uno espere eternamente en un bucle
- Que la leche se compre dos veces por la tarde
Solución
Respuesta: Que los dos dejen su nota, cada uno vea la del otro y ninguno compre
La exclusión mutua queda resuelta, pero si los dos dejan la nota a la vez, los dos se echan atrás y la nevera sigue vacía.
Solución con una variable turno compartida (while (turno != mi_numero) {}). Verdadero o falso:
- Garantiza exclusión mutua entre los dos procesos.
- Obliga a que los procesos entren alternándose, quieran o no.
- Cumple el requisito de progreso.
- Mientras espera, el proceso consume CPU dando vueltas al bucle.
Solución
- V — Garantiza exclusión mutua entre los dos procesos.
- V — Obliga a que los procesos entren alternándose, quieran o no.
- F — Cumple el requisito de progreso.
- V — Mientras espera, el proceso consume CPU dando vueltas al bucle.
Si al proceso 2 le toca el turno pero no quiere entrar, el 1 no puede volver a entrar aunque la sección esté libre: alternancia estricta, falla el progreso.
Sobre el algoritmo de Peterson para dos procesos:
- Cumple exclusión mutua, progreso y espera limitada
- Usa dos variables «quiero entrar» y una variable turno, todas compartidas
- Un procesador o compilador moderno que reordene lecturas y escrituras puede romperlo
- Funciona igual para cualquier número de procesos sin cambios
- No consume CPU mientras espera
- Necesita inhibir las interrupciones
Solución
Respuesta: Cumple exclusión mutua, progreso y espera limitada · Usa dos variables «quiero entrar» y una variable turno, todas compartidas · Un procesador o compilador moderno que reordene lecturas y escrituras puede romperlo
Peterson combina la nota con nombre (quiere1, quiere2) con la variable turno. Es correcto en teoría, pero solo para dos procesos, con espera activa y sensible a la reordenación de instrucciones.
Con Peterson, los dos hilos ponen su quiere a true. Después el hilo 1 escribe turno = 2 y justo después el hilo 2 escribe turno = 1. ¿Quién entra primero?
- El hilo 1
- El hilo 2
- Los dos a la vez
- Ninguno: se bloquean los dos
Solución
Respuesta: El hilo 1
El hilo 2 espera mientras quiere1 && turno == 1, y las dos cosas son ciertas. El hilo 1 espera mientras quiere2 && turno == 2, y turno vale 1: entra. El último que escribe turno es el que cede.
¿Qué es cierto de un mutex (cerrojo)?
- Tiene dos estados, libre y adquirido, y empieza libre
- Si un hilo hace lock() y el mutex ya está adquirido, ese hilo se bloquea hasta conseguirlo
- Al hacer unlock(), si hay hilos esperando, uno de ellos lo adquiere y se desbloquea
- Sus operaciones son lock() y signal()
- lock() nunca bloquea al hilo que lo llama
- Cada hilo tiene su propio mutex para su sección crítica
Solución
Respuesta: Tiene dos estados, libre y adquirido, y empieza libre · Si un hilo hace lock() y el mutex ya está adquirido, ese hilo se bloquea hasta conseguirlo · Al hacer unlock(), si hay hilos esperando, uno de ellos lo adquiere y se desbloquea
mutex = mutual exclusion. lock() adquiere (o bloquea), unlock() libera (y despierta a uno). Las dos operaciones son atómicas.
Sobre las variables condición:
- Siempre van asociadas a un mutex
- wait() bloquea al hilo y, mientras duerme, libera el mutex
- Si nadie espera, signal() no hace nada
- broadcast() despierta a todos los hilos de la cola
- signal() sin nadie en la cola se guarda para el próximo wait()
- Sustituyen al mutex: con ellas ya no hace falta lock()
Solución
Respuesta: Siempre van asociadas a un mutex · wait() bloquea al hilo y, mientras duerme, libera el mutex · Si nadie espera, signal() no hace nada · broadcast() despierta a todos los hilos de la cola
Una variable condición es una cola de hilos que esperan una condición dentro de una sección crítica. Al despertar, el hilo vuelve a adquirir el mutex antes de seguir.
P hace signal sobre una condición en la que espera Q. Relaciona:
| Estilo Hoare | |
| Estilo Mesa | |
| broadcast | |
| Desbloqueo FIFO |
Definiciones:
- Se despierta el que lleva más tiempo esperando
- Q se queda dormido hasta el siguiente cambio de contexto
- Se despiertan todos los que esperan y compiten por el mutex
- Q pasa a listo, pero espera a que P suelte el mutex
- Q sigue enseguida y P se queda sin el mutex hasta que Q termine
Solución
- Estilo Hoare → Q sigue enseguida y P se queda sin el mutex hasta que Q termine
- Estilo Mesa → Q pasa a listo, pero espera a que P suelte el mutex
- broadcast → Se despiertan todos los que esperan y compiten por el mutex
- Desbloqueo FIFO → Se despierta el que lleva más tiempo esperando
El estilo Mesa es el más usado hoy. Cuando hay varios esperando, la elección puede ser FIFO, por prioridades o al azar; broadcast los despierta a todos.
¿Por qué se escribe while (condicion) cond.wait(); y no if (condicion) cond.wait();?
- Porque con el estilo Mesa, cuando el hilo vuelve a tener el mutex la condición puede haber cambiado otra vez, y hay que comprobarla de nuevo
- Porque if no se puede usar dentro de una sección crítica
- Porque el while hace espera activa, que es más rápida
- Da igual: los dos son equivalentes
Solución
Respuesta: Porque con el estilo Mesa, cuando el hilo vuelve a tener el mutex la condición puede haber cambiado otra vez, y hay que comprobarla de nuevo
Entre el signal y el momento en que el hilo despierto recupera el mutex, otro hilo puede entrar y volver a llenar (o vaciar) el búfer. El while vuelve a mirar.
Herramientas de sincronización. Completa:
Los ____ los propuso Dijkstra en 1965. De los ____ de Hoare (1972) salen el mutex y las variables condición. Los ____ y los buzones no necesitan memoria ____.
Palabras:
- monitores
- interrupciones
- privada
- registros
- compartida
- semáforos
- canales
Solución
Los semáforos los propuso Dijkstra en 1965. De los monitores de Hoare (1972) salen el mutex y las variables condición. Los canales y los buzones no necesitan memoria compartida.
Con memoria compartida: semáforos, regiones críticas condicionales y monitores. Sin memoria compartida: canales, buzones y promesas y futuros.
En el problema del búfer finito con N huecos y un contador cola, ¿cuándo debe esperar el productor?
- Cuando cola == N
- Cuando cola == 0
- Cuando cola < N
- Nunca: el consumidor ya vaciará el búfer
Solución
Respuesta: Cuando cola == N
Productor: espera si está lleno (cola == N). Consumidor: espera si está vacío (cola == 0).
cola vale 4. Un productor hace cola++ y un consumidor cola-- a la vez, sin protección (cada uno lee, opera y escribe). ¿Con qué valores puede acabar cola?
- 4
- 5
- 3
- 6
- 2
Solución
Respuesta: 4 · 5 · 3
Si los dos leen 4 antes de que ninguno escriba, gana el último que escribe: 5 o 3. Solo el orden «uno entero y luego el otro» da el 4 correcto.
Lectores y escritores sobre una base de datos. Verdadero o falso:
- Pueden leer varios lectores a la vez.
- Un escritor puede escribir mientras hay lectores dentro, si no tocan la misma fila.
- Si se da prioridad a los lectores, un escritor puede esperar indefinidamente.
- Si se da prioridad a los escritores, desaparece todo riesgo de inanición.
Solución
- V — Pueden leer varios lectores a la vez.
- F — Un escritor puede escribir mientras hay lectores dentro, si no tocan la misma fila.
- V — Si se da prioridad a los lectores, un escritor puede esperar indefinidamente.
- F — Si se da prioridad a los escritores, desaparece todo riesgo de inanición.
Varios lectores o un solo escritor, nunca mezclados. Prioridad a los lectores → inanición de escritores; prioridad a los escritores → inanición de lectores.
En el problema de los cinco filósofos, ¿qué medidas evitan el interbloqueo?
- Que los pares cojan primero un palillo y los impares el otro
- Dejar que como mucho cuatro filósofos intenten coger palillos a la vez
- Coger los dos palillos en una sola operación atómica
- Que todos cojan primero el palillo de su izquierda
- Que esperen girando en un while hasta que haya palillo
Solución
Respuesta: Que los pares cojan primero un palillo y los impares el otro · Dejar que como mucho cuatro filósofos intenten coger palillos a la vez · Coger los dos palillos en una sola operación atómica
Si los cinco cogen el palillo izquierdo a la vez, cada uno espera el que tiene el vecino: interbloqueo. Las tres soluciones de los apuntes rompen ese círculo.
Si fallas varias de las soluciones de la leche, repasa qué requisito rompe cada intento. Una nota: exclusión mutua. Notas con nombre: progreso. Variable turno: progreso (alternancia estricta).
Trazas paso a paso: carreras, mutex y semáforos
Aquí se trata de seguir la ejecución instrucción a instrucción, como en las preguntas de cálculo. Las reglas para todas las tablas:
- Un semáforo nunca baja de 0.
waitresta 1 si puede; si vale 0, el proceso se bloquea en la cola. signaldespierta a uno si hay alguien en la cola (FIFO) y entonces el valor no cambia; si no hay nadie, suma 1.- En los estados escribe una sola palabra: dentro, fuera, bloqueado, imprime o listo, según la tabla. En Peterson, entra o espera. Las tildes y las mayúsculas dan igual.
El bote de la cena de clase, en una hoja compartida, tiene 200. El hilo 1 añade 30 y el hilo 2 saca 80 para un pago; cada uno lee, opera y escribe, y el planificador alterna un paso de cada uno. Rellena los registros privados (reg1, reg2) y el saldo en memoria después de cada paso:
| Paso | Hilo | Instrucción | reg1 | reg2 | saldo |
|---|---|---|---|---|---|
| 1 | 1 | leer | – | ||
| 2 | 2 | leer | |||
| 3 | 1 | sumar 30 | |||
| 4 | 2 | restar 80 | |||
| 5 | 1 | escribir | |||
| 6 | 2 | escribir |
Solución
| Paso | Hilo | Instrucción | reg1 | reg2 | saldo |
|---|---|---|---|---|---|
| 1 | 1 | leer | 200 | – | 200 |
| 2 | 2 | leer | 200 | 200 | 200 |
| 3 | 1 | sumar 30 | 230 | 200 | 200 |
| 4 | 2 | restar 80 | 230 | 120 | 200 |
| 5 | 1 | escribir | 230 | 120 | 230 |
| 6 | 2 | escribir | 230 | 120 | 120 |
Los dos hilos leen 200 antes de que nadie escriba. El hilo 1 escribe 230, pero el hilo 2 escribe después su 120 encima: los 30 añadidos se pierden. Lo correcto era 200 + 30 − 80 = 150 y sale 120.
Tres procesos A, B y C protegen su sección crítica con un semáforo m que empieza en 1 (un mutex). Rellena el valor de m y el estado de cada proceso (dentro, bloqueado o fuera) después de cada operación. La cola es FIFO:
| Paso | Operación | m | A | B | C |
|---|---|---|---|---|---|
| 1 | A: wait(m) | ||||
| 2 | B: wait(m) | ||||
| 3 | C: wait(m) | ||||
| 4 | A: signal(m) | ||||
| 5 | B: signal(m) | ||||
| 6 | C: signal(m) |
Solución
| Paso | Operación | m | A | B | C |
|---|---|---|---|---|---|
| 1 | A: wait(m) | 0 | dentro | fuera | fuera |
| 2 | B: wait(m) | 0 | dentro | bloqueado | fuera |
| 3 | C: wait(m) | 0 | dentro | bloqueado | bloqueado |
| 4 | A: signal(m) | 0 | fuera | dentro | bloqueado |
| 5 | B: signal(m) | 0 | fuera | fuera | dentro |
| 6 | C: signal(m) | 1 | fuera | fuera | fuera |
m baja a 0 cuando entra A, y B y C se ponen en cola. Cuando A hace signal, m NO sube: el permiso pasa directamente a B (el primero de la cola). Lo mismo con B → C. Solo el último signal, sin nadie esperando, devuelve m a 1.
En el laboratorio hay 2 impresoras y un semáforo impresoras = 2. Cuatro procesos piden impresora con wait y la devuelven con signal. Rellena el valor del semáforo y el estado de cada proceso (imprime, bloqueado o fuera). La cola es FIFO:
| Paso | Operación | impresoras | A | B | C | D |
|---|---|---|---|---|---|---|
| 1 | A: wait | |||||
| 2 | B: wait | |||||
| 3 | C: wait | |||||
| 4 | B: signal | |||||
| 5 | D: wait | |||||
| 6 | A: signal | |||||
| 7 | C: signal | |||||
| 8 | D: signal |
Solución
| Paso | Operación | impresoras | A | B | C | D |
|---|---|---|---|---|---|---|
| 1 | A: wait | 1 | imprime | fuera | fuera | fuera |
| 2 | B: wait | 0 | imprime | imprime | fuera | fuera |
| 3 | C: wait | 0 | imprime | imprime | bloqueado | fuera |
| 4 | B: signal | 0 | imprime | fuera | imprime | fuera |
| 5 | D: wait | 0 | imprime | fuera | imprime | bloqueado |
| 6 | A: signal | 0 | fuera | fuera | imprime | imprime |
| 7 | C: signal | 1 | fuera | fuera | fuera | imprime |
| 8 | D: signal | 2 | fuera | fuera | fuera | fuera |
Un semáforo contador cuenta impresoras libres. A y B se llevan las dos (2 → 0); C y después D se bloquean. Cada signal con alguien esperando le pasa la impresora sin tocar el valor; al final, sin nadie esperando, vuelve a subir a 1 y a 2.
Spotify con un búfer de 2 huecos: semáforos huecos = 2 y llenos = 0. D (descargador) mete un trozo y R (reproductor) saca uno; quien es despertado por un signal ya tiene su permiso y completa su operación en su siguiente turno. Rellena, después de cada turno, los trozos del búfer, huecos, llenos y el estado de D y de R (listo o bloqueado):
| Turno | Quién | Acción | Trozos | huecos | llenos | D | R |
|---|---|---|---|---|---|---|---|
| 1 | D | mete | |||||
| 2 | D | mete | |||||
| 3 | D | bloquea | |||||
| 4 | R | saca | |||||
| 5 | R | saca | |||||
| 6 | R | bloquea | |||||
| 7 | D | mete |
Solución
| Turno | Quién | Acción | Trozos | huecos | llenos | D | R |
|---|---|---|---|---|---|---|---|
| 1 | D | mete | 1 | 1 | 1 | listo | listo |
| 2 | D | mete | 2 | 0 | 2 | listo | listo |
| 3 | D | bloquea | 2 | 0 | 2 | bloqueado | listo |
| 4 | R | saca | 1 | 0 | 1 | listo | listo |
| 5 | R | saca | 0 | 1 | 0 | listo | listo |
| 6 | R | bloquea | 0 | 1 | 0 | listo | bloqueado |
| 7 | D | mete | 1 | 1 | 0 | listo | listo |
Turno 3: no quedan huecos y D se bloquea. Turno 4: R saca un trozo y su signal(huecos) despierta a D, así que huecos sigue a 0. Turno 6: el búfer está vacío y R se bloquea. Turno 7: D mete su trozo sin esperar (ya tenía el permiso) y su signal(llenos) despierta a R, así que llenos sigue a 0.
Algoritmo de Peterson, pero esta vez empieza el hilo 2. Rellena quiere1, quiere2 y turno después de cada instrucción y, en las filas del while, si el hilo entra o espera (entra / espera). Al principio quiere1 = quiere2 = false y turno = 1:
| Paso | Hilo | Instrucción | quiere1 | quiere2 | turno | Resultado |
|---|---|---|---|---|---|---|
| 1 | 2 | quiere2 = true | – | |||
| 2 | 2 | turno = 1 | – | |||
| 3 | 2 | while (quiere1 && turno == 1) | ||||
| 4 | 1 | quiere1 = true | – | |||
| 5 | 1 | turno = 2 | – | |||
| 6 | 1 | while (quiere2 && turno == 2) | ||||
| 7 | 2 | quiere2 = false | – | |||
| 8 | 1 | while (quiere2 && turno == 2) |
Solución
| Paso | Hilo | Instrucción | quiere1 | quiere2 | turno | Resultado |
|---|---|---|---|---|---|---|
| 1 | 2 | quiere2 = true | false | true | 1 | – |
| 2 | 2 | turno = 1 | false | true | 1 | – |
| 3 | 2 | while (quiere1 && turno == 1) | false | true | 1 | entra |
| 4 | 1 | quiere1 = true | true | true | 1 | – |
| 5 | 1 | turno = 2 | true | true | 2 | – |
| 6 | 1 | while (quiere2 && turno == 2) | true | true | 2 | espera |
| 7 | 2 | quiere2 = false | true | false | 2 | – |
| 8 | 1 | while (quiere2 && turno == 2) | true | false | 2 | entra |
El hilo 2 encuentra quiere1 = false y entra directamente. El hilo 1 levanta la mano, cede (turno = 2) y espera, porque el hilo 2 quiere y es su turno. En cuanto el hilo 2 pone quiere2 = false, la condición del hilo 1 deja de cumplirse y entra.
Subir el semáforo en un signal cuando había alguien bloqueado. Si hay cola, el permiso pasa directamente al despertado y el valor se queda igual: por eso en la traza del mutex m vale 0 durante cinco pasos seguidos.
Preguntas de desarrollo
Cuatro preguntas largas como las del examen. Escribe tu respuesta en la caja y después compárala con la respuesta modelo. No hay nota automática: tú decides si has cubierto cada punto de «lo que se valora».
¿Qué es una condición de carrera? Explícalo con dos hilos que incrementan el mismo contador e indica cómo se evita.
Solución
Respuesta modelo:
Una condición de carrera ocurre cuando dos o más hilos acceden a un dato compartido, al menos uno lo modifica y el resultado depende del orden en que se intercalen sus instrucciones.
Ejemplo: contador vale 7 y dos hilos hacen contador = contador + 1. En la CPU son tres pasos: leer a un registro, sumar y escribir. Si el hilo 1 lee 7 y le quitan la CPU antes de escribir, el hilo 2 también lee 7; los dos calculan 8 y los dos escriben 8. Se pierde una suma: debería valer 9.
Se evita convirtiendo el acceso al dato en una sección crítica con exclusión mutua: por ejemplo, rodeando la suma con mutex.lock() y mutex.unlock(), o con una instrucción atómica. Así, el segundo hilo no puede leer hasta que el primero haya escrito.
Lo que se valora:
- Definición: dato compartido, alguna escritura y resultado que depende del intercalado
- El incremento son tres pasos (leer, sumar, escribir) y no es atómico
- Un intercalado concreto que pierde una actualización, con los valores
- La solución: sección crítica con exclusión mutua (mutex, semáforo o instrucción atómica)
Lo importante es el porqué: el hilo trabaja con una copia en su registro y escribe un valor calculado a partir de un dato que ya está viejo.
Enumera y explica los tres requisitos de una solución al problema de la sección crítica. Después, razona si inhibir las interrupciones es una buena solución.
Solución
Respuesta modelo:
1. Exclusión mutua: nunca puede haber más de un proceso o hilo ejecutando su sección crítica.
2. Progreso: si nadie está en la sección crítica y hay procesos que quieren entrar, solo esos procesos participan en la decisión y esta se toma en un tiempo finito.
3. Espera limitada: existe un límite al número de veces que otros procesos pueden entrar antes que uno que ya ha pedido entrar.
Inhibir las interrupciones consigue exclusión mutua en un único procesador, porque sin interrupciones nadie expulsa al proceso. Pero no es buena solución general: suspende toda la concurrencia del sistema (perjudica a los que no quieren entrar), no sirve en multiprocesadores (cada CPU tiene sus interrupciones y otra puede entrar) y no garantiza espera limitada.
Lo que se valora:
- Los tres requisitos con su nombre exacto
- Una definición correcta de cada uno
- Por qué inhibir interrupciones da exclusión mutua en un procesador
- Sus tres desventajas: congela el sistema, no vale en multiprocesadores, no garantiza espera limitada
Un examen suele pedir el nombre exacto de los requisitos: exclusión mutua, progreso y espera limitada.
Compara un mutex y una variable condición: para qué sirve cada uno, qué operaciones tienen y qué pasa tras un signal en el estilo Hoare y en el estilo Mesa.
Solución
Respuesta modelo:
Un mutex sirve para tener acceso exclusivo a un recurso. Tiene dos estados (libre y adquirido) y dos operaciones atómicas: lock() lo adquiere o bloquea al proceso si ya estaba adquirido; unlock() lo libera y, si hay procesos esperando, uno de ellos lo adquiere.
Una variable condición sirve para esperar a que se cumpla una condición dentro de una sección crítica. Siempre va asociada a un mutex y gestiona una cola de procesos. Operaciones: wait() bloquea al proceso y libera el mutex mientras duerme (al despertar debe volver a adquirirlo); signal() despierta a uno de la cola (si no hay nadie, no hace nada); broadcast() despierta a todos.
Tras un signal de P con Q esperando: en el estilo Hoare, Q se reanuda inmediatamente y P pierde el mutex; en el estilo Mesa, Q se desbloquea pero espera a que P libere el mutex. Mesa es el más usado, y por eso la condición se comprueba con while y no con if.
Lo que se valora:
- Para qué sirve cada herramienta (exclusión frente a espera por una condición)
- Operaciones del mutex: lock y unlock, con su efecto
- Operaciones de la variable condición: wait (libera el mutex), signal y broadcast
- Hoare frente a Mesa y la consecuencia del while
La pista para no mezclarlas: el mutex responde a «¿puedo entrar?»; la variable condición, a «¿ya se cumple lo que espero?».
Problema del búfer finito: explica qué condiciones hay que respetar y cómo se resuelve con un mutex y dos variables condición (escribe el pseudocódigo del productor y del consumidor).
Solución
Respuesta modelo:
Productores y consumidores comparten una cola FIFO de N posiciones. El productor no debe meter si el búfer está lleno (cola == N) y el consumidor no debe sacar si está vacío (cola == 0). Además, cola++ y cola– tocan un dato compartido: sin protección, un intercalado puede dejar un valor incorrecto.
Productor: mutex.lock(); while (cola == N) cprod.wait(); buffer[in] = cosa; in = (in + 1) % N; cola++; ccons.signal(); mutex.unlock();
Consumidor: mutex.lock(); while (cola == 0) ccons.wait(); cosa = buffer[out]; out = (out + 1) % N; cola--; cprod.signal(); mutex.unlock(); consumir(cosa);
El mutex garantiza que solo uno toca buffer, in, out y cola. Cada uno espera en su variable condición y avisa al otro con signal después de cambiar el estado. Se usa while porque, con el estilo Mesa, al despertar hay que volver a comprobar la condición.
Lo que se valora:
- Las dos condiciones: lleno (cola == N) y vacío (cola == 0)
- El mutex rodea todo el acceso a buffer, índices y cola
- Cada lado espera en su condición y señala la del otro
- while y no if, por el estilo Mesa
- consumir() fuera de la sección crítica
consumir(cosa) va después de unlock(): no toca datos compartidos y, si estuviera dentro, haría esperar al productor sin motivo.
Todo el tema en un dibujo
Si solo puedes repasar una cosa antes del examen, que sea este mapa: el problema (datos compartidos y condición de carrera), la regla (sección crítica y sus tres requisitos) y las tres familias de soluciones.
Chuletario: concurrencia y sincronización
Lo esencial en 30 segundos
- Concurrencia = varios procesos existen a la vez. Paralelismo = se ejecutan a la vez en varios procesadores. Distribuida = paralelismo en red.
x = x + 1son tres pasos (leer, sumar, escribir): un cambio de contexto en medio provoca una condición de carrera.- Sección crítica = código que toca datos compartidos. Requisitos: exclusión mutua, progreso y espera limitada.
- Hardware: inhibir interrupciones (solo un procesador), test-and-set y compare-and-swap (espera activa, sin espera limitada).
- Sistema operativo: mutex (lock/unlock), variables condición (wait/signal/broadcast, siempre con un mutex) y semáforos (contador + cola).
- Clásicos: búfer finito, lectores y escritores, filósofos (interbloqueo).
Si te preguntan… / Respuesta
| Si te preguntan… | Respuesta |
|---|---|
| ¿Concurrencia implica ejecución simultánea? | No: eso es paralelismo |
| ¿Cómo se expresa la concurrencia en el código? | API (pthreads), objetos concurrentes (Java, C++), sentencias concurrentes (Ada, Go) |
| ¿Para qué sirve la programación concurrente? | Expresar el mundo real, aprovechar varios procesadores, facilitar algunos algoritmos |
| ¿Qué falla con una nota? | Exclusión mutua: compran los dos |
| ¿Qué falla con notas con nombre? | Progreso: puede que nadie compre |
| ¿Qué falla con la variable turno? | Progreso: alternancia estricta |
| ¿Qué cumple Peterson? | Los tres requisitos (pega: reordenación, dos procesos, espera activa) |
| ¿Inhibir interrupciones vale en multiprocesador? | No, y tampoco garantiza espera limitada |
| ¿Qué devuelve test-and-set? | El valor antiguo, tras ponerlo a true |
| ¿Qué devuelve CAS(M, A, B)? | Cierto si M = A (y entonces M ← B); falso si no |
| ¿Qué hace wait() en una variable condición? | Bloquea y libera el mutex mientras duerme |
| ¿signal() sin nadie en la cola? | Variable condición: nada. Semáforo: suma 1 |
| ¿Hoare o Mesa? | Hoare: Q sigue ya. Mesa: Q espera a que P suelte el mutex (el más usado) |
| ¿Herramientas sin memoria compartida? | Canales, buzones, promesas y futuros |
| ¿Prioridad a los lectores? | Inanición de los escritores (y al revés) |
| ¿Soluciones de los filósofos? | Asimétrico pares/impares, máximo cuatro pidiendo, coger los dos palillos atómicamente |
Plantillas
Sección crítica con mutex:
mutex.lock(); // sección crítica mutex.unlock();
Esperar una condición (estilo Mesa, por eso while):
mutex.lock(); while (!condicion) cond.wait(); // duerme y suelta el mutex // … ya se cumple mutex.unlock();
Búfer finito con semáforos (huecos = N, llenos = 0, m = 1):
// Productor // Consumidor wait(huecos); wait(llenos); wait(m); wait(m); meter(cosa); cosa = sacar(); signal(m); signal(m); signal(llenos); signal(huecos);
Peterson (proceso 1; el 2 es simétrico):
quiere1 = true;
turno = 2;
while (quiere2 && turno == 2) { }
// sección crítica
quiere1 = false;
Síntoma / Causa probable
| Síntoma | Causa probable |
|---|---|
| El contador acaba con menos de lo esperado | Condición de carrera: leer-sumar-escribir sin protección |
| Dos procesos dentro de la sección crítica | Falla la exclusión mutua (nota simple, interrupciones en multiprocesador) |
| Sección libre y nadie entra | Falla el progreso (notas con nombre, variable turno) |
| Uno nunca consigue entrar aunque el resto avanza | Inanición: falta espera limitada o hay prioridad para otro grupo |
| Nadie avanza y todos esperan a otro | Interbloqueo (filósofos con el palillo izquierdo) |
| La CPU al 100 % mientras un hilo espera | Espera activa (while que comprueba una y otra vez) |
| Un hilo despierta y el búfer sigue vacío | Se usó if en lugar de while con el estilo Mesa |
Antes de entregar, pregúntate
- ¿He distinguido concurrencia (existir a la vez) de paralelismo (ejecutarse a la vez)?
- ¿He escrito los tres requisitos con su nombre exacto: exclusión mutua, progreso y espera limitada?
- En las trazas, ¿he comprobado si había alguien en la cola antes de sumar en un
signal? - ¿Sé decir qué requisito rompe cada solución y cuál es la pega de las que los cumplen todos?
Con esto termina el bloque de procesos de IC2. Si quieres repasar, vuelve a la teoría de concurrencia y sincronización o a los ejercicios de planificación de la CPU. El siguiente tema es la gestión de memoria: memoria contigua, segmentación y paginación.
Sigue aprendiendoTodos los artículos de Sistemas operativos¿Te ha servido este artículo? Así puedes apoyar SergioLearns →
¿Listo para el examen?Simulacro del examen de teoría de IC2 — primer parcial, segundo parcial o convocatoria, con tu nota real y el repaso de cada fallo (como si estuvieras en el aula)


