Informe
Informe
Informe
Integrantes:
Juan Manuel Gil
Francisco Ciordia Cantarella
Julieta Prieto
Gina Commisso
ÍNDICE
NOMENCLATURA 3
PRESENTACIÓN DE LA RED 4
Gráfico 4
CLASIFICACIÓN 5
ANÁLISIS DE LOS INVARIANTES 6
ANÁLISIS DE CONFLICTOS Y DEADLOCK 9
ANÁLISIS DE CONCURRENCIA 10
IDENTIFICACIÓN DEL PROBLEMA 11
PROPUESTAS DE SOLUCIONES 11
SOLUCIÓN 1 12
SOLUCIÓN 2 16
SOLUCIÓN 3 21
TABLAS DE ESTADOS Y EVENTOS 24
TABLA DE ESTADOS DE LA RED ORIGINAL 24
TABLA DE EVENTOS DE LA RED ORIGINAL 25
CAMBIOS A LAS TABLAS SEGÚN LAS SOLUCIONES PROPUESTAS 25
GRÁFICOS DE HILOS 27
Política 28
Monitor 29
Implementación en Java 29
Semántica Temporal 31
Consideraciones 34
Conclusiones 37
Update#1 37
Referencias 40
NOMENCLATURA
A lo largo del trabajo, haremos referencia a las siguientes definiciones de
elementos[6]:
PRESENTACIÓN DE LA RED
Gráfico
Clasificación
Como la red tiene T-invariantes positivos, es probable que sea acotada y viva. Viva
quiere decir que sea cual sea la evolución del marcado, todas las transiciones se
van a poder volver a disparar. Es decir, está la posibilidad de que no haya
interbloqueo, pero aun no lo comprobamos. Acotadas hace referencia a que las
plazas están delimitadas por un marcado inicial y por un entero que limita la
cantidad de tokens que pueden tener.
Ciclos/bucles del sistema [IMG 2]
1. Sección crítica
3. Exclusión mutua
Recurso en p10 que puede ser utilizado por t4 o t8 pero no de forma
simultánea.
4. Capacidad finita (LIMITADOR-2)
5. Secuencia
Varios disparos en forma ordenada.
6. Sección crítica
7. Conflicto en P2.
ANÁLISIS DE CONFLICTOS Y DEADLOCK
Análisis de los espacios de estados
Conflictos estructurales
DEADLOCK
Este es uno de los casos en que se puede dar deadlock ya que de manera similar
ocurre cuando hay un token en la plaza p11 en vez en lugar de la p9.
ANÁLISIS DE CONCURRENCIA
Para ello veremos el promedio de tokens marcados en el color oscuro ya que hacen
referencia a los hilos en procesos.
Bloque Deadlock
PROPUESTAS DE SOLUCIONES
A continuación se presentarán las diferentes soluciones aplicables para este
problema y la elección de nuestra solución final.
SOLUCIÓN 1
Para evitar la situación en donde un hilo ejecutando un recurso en exclusión mutua,
se encuentre con que los recursos necesarios para continuar estén tomados por los
hilos de la fuente contraria, hay que tener en cuenta los Marcados previos que nos
llevan al deadLock:
Nos encontramos con un estado en donde tenemos 2 toquen en una de las plazas
de inicio a los flujos (p6 o p15), y al menos 1 toquen o más en la la plaza restante.
Estos marcados pueden llevarnos a un deadlock. Para esta solución decidimos
evitar estos marcados y para ello vamos a poner un semáforo entre dichas plazas
inicializado en 2.
Para ello veremos el promedio de las casillas de tono más oscuro que hacen
referencia a los hilos en procesos.
SOLUCIÓN 2
Para esta solución, nos enfocamos en la ACCIÓN previa al deadLock, en el
preMarcado al deadlock.
Anteriormente lo que hacíamos era evitar este marcado, en cambio ahora solo
evitaremos los pre marcados en los cuales es inevitable llegar al deadlock, y en
caso contrario solo las transiciones que con dicho marcado nos lleven a un
deadlock.
En esta situación, vemos que si sacamos el token de P1, nos quedamos con
todas las transiciones de los laterales desensibilizadas.
● ANÁLISIS DE CONCURRENCIA:
En esta solución se dan todas las situaciones anteriores pero además se quita la
restricción de que la suma de los tokens sea 2, ya que ahora pueden ser 3 tokens
en simultáneo en las plazas siempre que no haya más de 2 tokens en cada 1. Es
decir, a diferencia de la solución anterior, ahora se hace un mejor aprovechamiento
de los recursos ya que condiciona a un solo recurso inactivo presente en la plaza p7
o p14, y por ende más tareas habilitadas para ejecutarse por los actores.
Para ello veremos el promedio de tokens marcados en el color oscuro ya que hacen
referencia a los hilos en procesos.
En la siguiente imagen se puede ver que dentro de todo se mantiene, aunque para
las plazas de acceso al flujo en cada proceso disminuyen en promedio, esto quiere
decir que disminuye la concurrencia para estas actividades ya que se ven afectadas
por las plazas de acceso al flujo contrario.
SOLUCIÓN 3
Para la siguiente solución pensamos que era suficiente eliminar el deadlock limitando la
cantidad de tokens de las plazas p6 y p15 al valor de 1 (uno).
● ANÁLISIS DE LOS RESULTADOS DE LA RED
● ANÁLISIS DE CONCURRENCIA:
Decisión final:
Suma máxima 10 10 8 10
p2 Granos clasificados.
p3 Sifones disponibles.
p6 Cereza despulpada.
p9 Grano fermentado.
t2 Incinerar
t3 Despulpado
t4 Fermentado
t5 Lavado
t7 Mantenimiento de la máquina de
lavado.
t8 Mantenimiento de la máquina de
fermentado.
GRÁFICOS DE HILOS
Política
La politica es el arte de engañar - Nicolas Maquiavelo
El único requerimiento que se pidió para la política es que al momento de consultarle, esta
sea capaz de decidir a una sola opción, es decir, mantener el programa funcionando sin que
intenten ejecutar 2 acciones o más al mismo tiempo, pero al mismo tiempo ninguna. Esto en
primera instancia se implementó con una función random, es decir, observaba las trans con
la capacidad de disparar y de manera azarosa, devuelve una transición, a la larga esto
debía mantener los invariantes sin diferencias entre sí, este deberia era lo que a algunos
miembros del grupo no les gusto.
¿Qué se hizo entonces? Un sistema de registro donde la política tiene en cuenta cuantas
veces se disparó cada invariante, y en base a esto tomar sus propias decisiones, de esta
manera nos aseguramos que por criterio de la política, los invariantes se mantengan
ejecutándose de manera equitativa.
Monitor
No puedes controlar el viento, pero si la dirección que pones la vela - Randy Gage
Los hilos ingresan al monitor, ven si pueden disparar, y de poder hacerlo, lo intentan, para
esto deben estar sensibilizados (no vamos a hablar del tiempo aun). Si es posible, se
dispara, sino, se va a una cola de condición de esa transición.
El señalizado de las condiciones se hizo con la política de “Signal and Exit”, de tal forma
que cada hilo al terminar de ejecutarse verifica si hay alguna transición para despertar.
Todo esto se ejecuta en sección crítica, es decir, de manera atómica, a fines de evitar que
más de un hilo modifique la red y genere un problema de concurrencia.
Implementación en Java
El código respeta las convenciones estándares dadas por oracle para la programación en
java [3]. Para no extendernos en cuestiones inherentes al código, vamos a hacer hincapié
en cuestiones de diseño que se consideren importantes.
Factory: Con el fin de abstraer la creación de los “obreros” y los hilos, se implementaron
patrones factory los cuales permiten obtener las instancias deseadas sin la necesidad de
hacer uso de “new” de esta forma, generamos una capa de abstracción entre el programa y
el main.
Singleton: Dentro del programa hay clases las cuales requieren que solamente exista una
instancia del mismo, con este objetivo, implementamos el patrón Singleton, el cual sustituye
la llamada a “new” por un “getInstanceOf”. [4]
Observer: A decir verdad, no se usa el patrón en sí. En momentos tempranos del código, se
implementó y funcionaba, sin embargo en clase se utilizó una clase llamada “Exchanger”
[5], la cual mediante una variable de condición, brindaba un intercambio de mensajes más
provechoso y generando una espera activa entre los hilos. En etapas finales del codigo se
decidió agregar una GUI para ver la carga de los invariantes de la ejecución, usando este
mismo principio. Quizas en este punto se podría haber aplicado algo mas parecido al patron
para priorizar la encapsulación de las clases, sin embargo el uso de exchanger sigue dando
un mejor rendimiento en el intercambio de mensajes y de que las salidas del programa no
esten trabajando innecesariamente. Quizás si el proyecto necesitara escalar mas en este
aspecto, sería necesaria una refactorización.
Constantes: Donde se colocan los parámetros que se utilizan en el programa, para poder
cambiar de manera sencilla si se quieren probar cosas nuevas.
Ya en este punto es posible verificar el funcionamiento del programa, de tal manera que
ejecutamos 1000 transiciones sin semántica temporal.
Semántica Temporal
Bien, esta sección fue la que más tiempo llevo. Si bien la idea estaba clara y teníamos los
diagramas vistos en clases para guiarnos por donde debían ir los tantos.
En el proyecto se utilizó una semántica temporal de tiempo débil, por lo tanto existe una
ventana de tiempo donde la transición está sensibilizada, a partir de un tiempo Alpha hasta
un tiempo Beta.
En primera instancia se agregaron alfa y betas al azar a todas las transiciones por igual. Y
funcionaban ya que serializabamos la espera de la transición. Con el grupo consideramos
que esto era un error ya que debería poder darse la oportunidad de que otra transición
intente disparar si su alfa se cumple primero, así que hicimos que se libere el mutex cada
vez que una trans esperaba su ventana temporal.
Esto generó otro problema, varios hilos se quedaban esperando por la ventana temporal de
la misma transición. Esto se soluciono creando un arreglo que identificaba si había alguien
esperando ya por esa transición.
Pero al momento de hacer las pruebas algo andaba mal, obtenemos estos resultados:
Siendo este el peor resultado posible, había casos donde el invariante 2 se ejecutaba un par
de veces, sin embargo, sin ser suficiente para considerar que funcionaba bien.
“La política decide, pero la semántica temporal va a ser al final quien decida quién
disparar”
Esto llevó a distintas pruebas, al principio se creía que mientras más lento sea el invariante
3 y más rápido el 2, intuitivamente mientras más tarda el 3 en sensibilizarse, más tiempo
tiene el invariante 2 para ejecutarse. Sin embargo teníamos un funcionamiento del 60%,
entre ejecuciones que cumplian el criterio y otra que no. Pero finalmente la idea más
determinante fue la siguiente; El problema se da cuando el invariante 3 mantiene mucho
tiempo los tokens y el 2 muy poco, entonces la solución es al revés de lo que se planteó
antes. Y así fue.
En la pc que se testeo en una pc de 2 cores, en la cual funcionó bien, pero a medida que
aumentamos los cores el programa volvió a fallar. Finalmente testeando todo en una pc de
12 cores, pudimos dar con el umbral de los alfas para que el programa ande bien sin
importar la cantidad de cores de la pc donde se ejecute, (O eso se intuye).
Para asignar los alphas, ponderamos el tiempo máximo que se le asigna al invariante 2
(Siendo este el más damnificado por el actuar de la semántica temporal). Notamos que el
valor umbral que se le puede asignar, es de 8 milisegundos, a partir de ahí deja de funcionar
con la mayoría de las veces. (Notar que esto está más relacionado a la ponderación al
asignar los tiempos, y que se podrían hacer otras asignaciones, cambiando este umbral). Al
hacer las pruebas de disparo obtuvimos los siguientes resultados:
Viéndose una tendencia al invariante 3 ya que tiene una ventana temporal menor y la
Transición 6 que no está temporizada. Pero es un resultado excelente que en contraste con
la anterior muestra cómo la semántica temporal afecta a la decisión final de la política que a
pesar de intentar mantener los disparos simétricos, no lo logra.
Respecto al Beta, se seleccionó uno de tal manera que no afecte nuestra ejecución y de un
riesgo de deadlock, sin embargo, se aseguró que cada tanto se de el evento de que una
ventana de tiempo se supere. En ese caso salta el siguiente mensaje:
Consideraciones
Previamente hablamos de cosas que se agregaron para asegurarnos que la concurrencia
funcione.
Se implementó una función que verifique en cada disparo que no se violen los invariantes
de plaza, para encontrarnos que es bastante difícil romperlo hasta a propósito.
También se agregó un cortafuego en caso que el mutex se rompa.
Además, al finalizar la ejecución, viendo las transiciones que sobraron, podemos ejecutarlas
manualmente en el Pipe a partir del home state y comparar los marcados finales, si estos se
cumplen es porque se mantuvieron los invariantes de transición.
También como se vio en las imágenes, se implementó una interfaz más amigable con el
usuario (Respecto a la CLI que teníamos hasta el momento de la decisión), que va
mostrando la distribución de la carga de los invariantes de manera simple.
Conclusiones
A través de los mecanismos de concurrencia dados a lo largo de la materia podemos
asegurar que la concurrencia funciona correctamente.
También cabe nombrar la situación en la cual nos percatamos en una etapa muy avanzada
del proyecto que cometimos un error conceptual al asignar las tareas de los hilos, pero que
este error era justamente el que hacía que los test pasaran, cuando corregimos ese error, el
programa dejaba de funcionar correctamente (Consideramos que fue una falta de
comunicación entre los integrantes del grupo lo que hizo que 2 ideas distintas avancen y no
se corrijan juntas, dejando una parte del código en mal estado hasta las últimas revisiones).
Para corregirlo fue necesario refactorizar varias áreas del código, lo cual dejó bien en claro
lo importante que fue crear el código con el menor acoplamiento posible, ya que a pesar de
haber sido un cambio importante en muchas líneas, gran parte del proyecto se mantuvo ya
que su funcionamiento era independiente de las otras clases.
Update #1
Luego de la primera exposición del trabajo se detallaron un conjunto de problemas que
algunos eran mínimos y otros se volvían determinantes para verificar el correcto
Código:
El primer problema era la política de señalizado. La cual daba prioridad a la cola de entrada
en lugar de a la cola de condición, esta fue un decisión de diseño adrede pero en palabras
de los profes, no se puede asegurar el cumplimiento de la política; ya que esta decidia quien
se liberaba, pero no se aseguraba que sea la próxima en pasar la cola de entrada.
Otra corrección fueron partes en el código donde el profe no le gustaba la forma que se
implemento o sectores que considero que era preferible otro tipo de implementación. Las
mismas se analizaron y se decidió mejores alternativas.
Otras correcciones eran cosas que se prefería cambiar y que nos percatamos de ello en los
procesos previos a la primera corrección, y se aprovechó esta instancia para ello.
Es posible verlos en el repositorio analizando los commits posteriores al
“af88fad74c968a8713bd8f45b6d95ee92fbb48fb”.
Regex y Script:
El script de python dio una respuesta errónea durante la exposición. El error se aisló y se
visualiza mejor en la siguiente imagen.
En la red existen 2 invariantes que comparten la misma transición (T0). De modo que los
invariantes son T0, T1, T10 conocido como invariante 1 y T0, T2, T3, T4, T5, conocido
como invariante 2.
Lo que sucedía en el video es que el último invariante 2, quedó sin terminar, y además se
ejecutó un invariante 1 completo, quedando un log de la siguiente forma.
T0 T2 T3 T0 T1 T4 T10
Lo que se ve que sucede, es que al faltar el último T5, la regex interpreta el primer T0 como
parte del invariante 1. De tal forma:
T0 T2 T3 T0 T1 T4 T10 -> T2 T3 T0 T4 como sobrantes. (Siendo en ese orden, un
conjunto de transiciones invalida)
Entonces, lo que había que revisar era que de alguna manera, el regex identificara que el
T0 previo perteneciera a un T1 o a un T2. Eso se logró implementando una herramienta de
las regex llamada Lookaround [7]. Siendo la nueva regex, la siguiente.
Si comparamos con la regex anterior vemos que:
Diagramas:
Se pidió que se mejore estéticamente el diagrama de clase y se rehaga el diagrama de
secuencia, ya que la perspectiva de alto nivel no era la que correspondía a las intenciones
de explicar el trabajo.
Se pueden comparar los diagramas rápidamente y ver que se han agregado clases y
comportamientos que en el diagrama anterior se consideraban tácitos.
Diagrama anterior: diagrama_secuencia-Page-1.png
Diagrama Nuevo: DiagramaDeSecuencia_v3.0.jpg
Referencias
[1] ¿Cómo se produce el café? (2019, November 4). Universidad del Claustro de Sor
https://www.elclaustro.edu.mx/claustronomia/index.php/mundo-foodie/item/400-como
-se-produce-el-cafe
[3] Oracle. (1996, October 4). Java Code Conventions. Oracle. Retrieved December 29,
[4] 5 formas de implementar el patrón Singleton en Java. (2020, November 21). Blog Bitix.
https://picodotdev.github.io/blog-bitix/2020/11/5-formas-de-implementar-el-patron-sin
gleton-en-java/
[5] Oracle. (n.d.). Exchanger (Java Platform SE 7 ). Oracle Help Center. Retrieved
https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Exchanger.html
[6] Petri nets: Structural analysis from ,
http://www.lsv.fr/~schwoon/enseignement/verification/ws0910/nets2
[7] lookaround - Regex lookahead, lookbehind and atomic groups. (2010, June 4). Stack
https://stackoverflow.com/questions/2973436/regex-lookahead-lookbehind-and-atomi
c-groups