Problemas comunes en programación concurrente

Pregunta 1B:
El principio de la bandera es un teorema que podemos usar para comprobar si está garantizado la exclusión mutua para dos procesos en un código concreto. Si dos procesos primero levantan sus banderas y después miran al otro lado por lo menos uno de los procesos ve la bandera del otro levantado.

Se puede comprobar con contradicción:

Asumimos P0 era el último en mirar

Entonces la bandera de P0 está levantada

Asumimos que P0 no ha visto la bandera de P1

Entonces P1 ha levantado la bandera después de la mirada de P0

Pero P1 mira después de haber levantado la bandera

Entonces P0 no era el último en mirar

Pregunta 2B:

Introducir comandos sleep después de bloques sincronizados en un programa concurrente puede alterar su comportamiento, ya que cambia el orden en que los hilos acceden a los recursos compartidos. Este enfoque puede ayudar a detectar problemas de concurrencia como condiciones de carrera o deadlocks, pues el sleep forza un cambio en el orden de ejecución de los hilos, pudiendo revelar problemas.


Sin embargo, esta técnica tiene limitaciones. El programa puede tener problemas de concurrencia no detectables con la introducción de los comandos sleep. Además, estos comandos ralentizan las pruebas, lo que puede ser problemático en entornos de producción donde el tiempo es valioso. Por último, los comandos sleep pueden crear problemas de rendimiento, ya que los hilos en ‘sleep’ consumen recursos del sistema sin realizar trabajo útil.

Pregunta 3B:

Deadlock y livelock son condiciones indeseables que pueden ocurrir en sistemas concurrentes:

1. Deadlock: Sucede cuando dos o más procesos quedan bloqueados esperando a que el otro libere un recurso necesario para su ejecución. Ninguno puede seguir adelante, resultando en una paralización del sistema. Los recursos retenidos se desperdician, y la solución puede requerir intervención manual, lo que podría causar inactividad en el sistema.

2. Livelock: Se parece al deadlock, pero los procesos están en un constante cambio de estado en respuesta a los demás, en lugar de estar bloqueados. Aunque los procesos parecen estar activos, no realizan un trabajo significativo. Esto puede degradar el rendimiento del sistema y, como en el deadlock, puede requerir intervención manual para resolverlo.


Pregunta 1A: Al considerar un nuevo protocolo para garantizar la exclusión mutua en una sección crítica, debes analizar las siguientes propiedades:

– Exclusión mutua: Solo un proceso debe tener acceso a la sección crítica a la vez.

– Ausencia de espera indefinida: Cada proceso que solicita acceso debe obtenerlo en un tiempo finito.

– Progreso: La decisión de quién entra a la sección crítica no debe ser pospuesta indefinidamente si hay procesos esperando.

– Espera limitada: Debe existir un límite en cuántas veces otros procesos pueden entrar en la sección crítica tras una solicitud y antes de que se conceda.

Para verificar la exclusión mutua, debes ejecutar pruebas de unidad e integración donde varios procesos intenten acceder a la sección crítica simultáneamente. Revisa el código para asegurar que se utilizan mecanismos de control de acceso, y busca operaciones que modifiquen los datos compartidos para confirmar que están correctamente protegidos y no presentan situaciones de carrera.

Pregunta 2A: Introducir comandos de ‘sleep’ con una duración aleatoria delante de los ‘wait()’ en un programa concurrente puede tener efectos y proporcionar información útil durante la depuración. Sin embargo, hay consideraciones importantes:

1. El programa debería funcionar lógicamente igual, pero pueden surgir problemas de sincronización al desequilibrar las sincronizaciones existentes.


2. Las condiciones de carrera pueden ser amplificadas o enmascaradas al introducir pausas aleatorias, lo que puede conducir a resultados incorrectos.

3. Si el programa es sensible al tiempo o tiene requisitos estrictos de rendimiento, las pausas adicionales pueden afectar negativamente su comportamiento.

4. Introducir pausas aleatorias no reemplaza las técnicas de depuración tradicionales y puede enmascarar errores o introducir nuevos problemas.

5. Las pruebas en general consumen tiempo adicional en el desarrollo de software.

En resumen, las pausas aleatorias pueden revelar problemas de sincronización y condiciones de carrera, pero también conllevan riesgos y consideraciones de rendimiento.

Pregunta 3A: Para conseguir un algoritmo correcto (aunque todavía no eficiente) teníamos que resolver dos problemas:

La operación Add no tenía carácter de atomicidad, es decir, si se intercalaba con otra operación de otro hilo, el resultado fue no-determinista.

La condición del bucle y la operación en el bucle estaban acoplados, es decir, la semántica dependía de las acciones de los demás hilos (aunque cada operación individual sí era atómica)


Estas dos variantes de problemas están conocidas como conceptos de linerarizabilidad y serializabilidad.

El algoritmo no era eficiente, ya que hay mucha congestión accediendo a las variables compartidas q y r con exclusión mutua: En cada iteración del bucle de cálculo todos los hilos compiten por el acceso exclusivo a q y r. Es más eficiente dividir el trabajo por hacer en trozos que puede realizar cada hilo independiente de los demás. Y solamente al final se une los resultados individuales.

Pregunta 4A: Se utiliza la BlockingQueue de la siguiente manera:

Los productores colocan elementos (platos) en la BlockingQueue usando el método put(). Si la cola está llena, el hilo productor se bloquea hasta que haya espacio disponible.

Cuando todos los elementos han sido producidos, se coloca un ‘plato vacío’ (representado por el número -1) en la cola. Este es un indicador de que no se producirán más elementos.

Los consumidores toman elementos de la BlockingQueue usando el método take(). Si la cola está vacía, el hilo consumidor se bloquea hasta que haya un elemento disponible.

Cuando un consumidor encuentra un ‘plato vacío’, sabe que no hay más elementos para consumir, vuelve a colocar el ‘plato vacío’ en la cola, y luego finaliza su ejecución.

De esta manera, la BlockingQueue facilita la sincronización entre productores y consumidores y permite manejar la finalización de manera segura.


Pregunta 1C: En Java, el concepto de región crítica se implementa utilizando la palabra clave ‘synchronized’ y bloques ‘synchronized’. Un bloque ‘synchronized’ garantiza que solo un hilo pueda ejecutar el código dentro de la región crítica a la vez, utilizando un objeto como monitor para la sincronización. Las ventajas incluyen la capacidad de manejar la concurrencia de forma estructurada, evitar problemas de sincronización y condiciones de carrera, simplificar la escritura de código concurrente y permitir el acceso seguro a recursos compartidos. Sin embargo, el uso excesivo puede disminuir el rendimiento y existe el riesgo de bloqueo o problemas sutiles de sincronización. En entornos distribuidos, se requieren mecanismos más complejos para la sincronización y comunicación entre hilos distribuidos, ya que las implementaciones de región crítica en Java no son directamente aplicables.

Pregunta 2C:

Introducir comandos de ‘sleep’ justo antes de los ‘wait’ existentes en un programa concurrente puede afectar el comportamiento del programa. Aunque el programa debería funcionar lógicamente, pueden surgir problemas de sincronización, afectar la sensibilidad al tiempo, dificultar la reproducibilidad de errores y generar una dependencia excesiva del tiempo. Es importante diseñar cuidadosamente las pruebas, considerar los posibles problemas y evaluar el impacto de las demoras. Además, las pruebas con demoras siempre consumen tiempo adicional en el desarrollo de la aplicación.


Pregunta 3C:

Para conseguir un algoritmo correcto (aunque todavía no eficiente) teníamos que resolver dos problemas:

La operación Add no tenía carácter de atomicidad, es decir, si se intercalaba con otra operación de otro hilo, el resultado fue no-determinista.

La condición del bucle y la operación en el bucle estaban acoplados, es decir, la semántica dependía de las acciones de los demás hilos (aunque cada operación individual sí era atómica)

Estas dos variantes de problemas están conocidas como conceptos de linerarizabilidad y serializabilidad.

El algoritmo no era eficiente, ya que hay mucha congestión accediendo a las variables compartidas q y r con exclusión mutua: En cada iteración del bucle de cálculo todos los hilos compiten por el acceso exclusivo a q y r. Es más eficiente dividir el trabajo por hacer en trozos que puede realizar cada hilo independiente de los demás. Y solamente al final se une los resultados individuales.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.