Saltar navegación

PSP Semáforos en JAVA - Contenido educativo

Ajuste de pantalla

El ajuste de pantalla se aprecia al ver el vídeo en pantalla completa. Elige la presentación que más te guste:

Subido el 15 de noviembre de 2022 por Stefano C.

54 visualizaciones

Descargar la transcripción

Si os acordáis, la última vez hablamos de sección crítica, que es la parte del programa que tiene que estar protegida. 00:00:00
Sería la parte donde puedan surgir algunos de los problemas que hemos visto en clase, como la inconsistencia de memoria o la discondition. 00:00:12
Por lo tanto, la sección crítica hay que marcarla de alguna forma para evitar que dos tres accedan a recursos compartidos a la vez y causen problemas de condición de carrera. 00:00:23
Por lo tanto, lo que tenemos que hacer es marcar en alguna parte del código donde acaba la sección crítica para que varias secciones críticas no se puedan ejecutar a la vez. 00:00:39
En general, si yo tengo un recurso compartido, cada vez que usaré este recurso compartido y que el uso de este recurso compartido vaya en contra de las condiciones de Bernstein, 00:00:54
que son estas tres, entonces lo que tenemos que hacer es marcarlo como sección crítica. 00:01:05
Para marcar la sección crítica hay dos formas sencillas de hacerlo. 00:01:12
Una son los semáforos, que son un poquito más a bajo nivel, y luego están los monitores. 00:01:18
En este vídeo vamos a ver los semáforos. 00:01:23
Los semáforos son sustancialmente una abstracción de lo que es un semáforo de verdad, en el sentido que cuando es verde tú puedes acceder al recurso, 00:01:26
cuando el semáforo se pone rojo tú te tienes que parar y esperar a que sea tu turno para poder utilizarlo. 00:01:37
Un semáforo se representa con una variable entera, que representa el número de instancias que tenga a disposición. 00:01:44
En general, yo podría tener varios recursos compartidos y poder, por ejemplo, tres impresoras, 00:01:54
y entonces mientras que tres threads quedan a utilizar la impresora, hasta tres hay posibilidad de utilizarla. 00:02:02
Cuando llegaría el cuarto thread que quiere utilizar uno de estos recursos, 00:02:10
pues entonces se debería bloquear esperando a que una de las tres impresoras se libere en un cierto sentido. 00:02:14
Entonces en la fase de inicialización de los semáforos se le pone un valor inicial, 00:02:21
que es el número de recursos disponibles en ese momento. 00:02:26
Cuando alguien adquiere el control sobre uno de estos recursos, se restará uno hasta que el valor sea cero. 00:02:30
Si el valor es cero, entonces el programa no permitirá seguir adelante y tendrá que bloquear el acceso a estos recursos. 00:02:38
Cuando alguno de los threads ha usado el recurso y lo libera, pues entonces el número asociado con el semáforo subirá. 00:02:46
Un hilo ejecuta una wait. Wait es una operación general, ¿vale? No la penséis en Java. 00:02:54
En general cuando hablamos de semáforos hay dos operaciones, la wait y la signal. 00:03:05
La wait sustancialmente es la que adquiere el elemento, adquiere el recurso compartido. 00:03:11
Si el valor es menor que cero es que no hay estantes disponibles 00:03:22
y podría ser utilizado este valor menor que cero para saber cuántos hilos están a la espera de un determinado recurso. 00:03:27
El hilo se bloquea y queda a la espera de que se liberen. 00:03:36
Cuando se liberen estos recursos se utiliza la operación signal, ¿vale? 00:03:42
La operación signal es como señalar a todos lo que están esperando que ahora hay un recurso libre. 00:03:47
Entonces lo que tendrán que hacer los threads que estaban esperando este recurso es competir entre ellos 00:03:53
para poder sustancialmente adquirir el recurso ellos, volver a restar uno al valor del semáforo 00:04:00
y por lo tanto todos los demás se mantendrán otra vez a la espera. 00:04:09
La wait y la signal tienen que ser atómicas. 00:04:13
Es decir, que tienen que ser operaciones que cuando lanzo el pillar el recurso o soltar el recurso 00:04:16
se tienen que hacer todo seguido. 00:04:23
No puedo pararme a mitad de una operación de wait o de signal. 00:04:28
Y hay soluciones de distintos tipos, tanto hardware como software, para poder implementar realmente estas cosas 00:04:32
como test and setup, swap o algoritmo de aplicación. 00:04:39
Que nosotros son bien interesantes pero no vamos a profundizar en estos. 00:04:42
Lo que sí vamos a ver es que Java nos proporciona una clase semáforo en el paquete java.util.concurrent 00:04:46
que implementa todo lo que necesitamos. 00:04:53
En particular implementa el equivalente de una wait y de una signal. 00:04:56
Y nosotros podemos utilizarlo para marcar nuestras secciones críticas. 00:05:00
Cuando un semáforo es binario, es decir, que cuando el valor que le damos al semáforo aquí 00:05:05
que se presenta con una variable entera es 1, entonces hay un solo recurso 00:05:13
se está implementando un tipo de semáforo especial que es el mutex. 00:05:19
Sustancialmente quiere decir que solo un thread puede en un determinado momento acceder a ese recurso. 00:05:24
Porque si está a 1 el recurso está disponible, si está a 0 el recurso no está disponible. 00:05:31
Y es, sustancialmente estamos haciendo lo que es una mutuexclusión. 00:05:36
Es decir, que solo un thread puede en un determinado momento acceder a ese recurso. 00:05:42
Esto por ejemplo nos puede servir cuando utilizamos el acceso a una variable, una variable compartida 00:05:48
y tenemos que escribir en esa variable. 00:05:54
Pues como en cada momento puede escribir solo un thread en esa variable, 00:05:56
pues entonces creamos un semáforo con valor 1 para que si yo adquiero el semáforo, 00:06:00
adquiero la posibilidad de utilizar el semáforo en un determinado momento. 00:06:06
De esta forma evito que dos threads intenten escribir a la vez en la misma variable o en el mismo objeto. 00:06:11
Entonces vamos a hacer un ejemplo. 00:06:25
Entonces vamos a ver cómo se puede utilizar ejemplos de semáforos en mutex. 00:06:29
Por ejemplo, si os acordáis el ejercicio que sumaba 10.000 veces 1 a una variable 00:06:36
y habíamos visto que generaba posibles problemas, 00:06:44
pues antes de empezar a ver una posible solución, tenemos que saber que la clase semáforo, 00:06:48
que tiene un constructor semáforo con un entero que es el número de instancias libres de recurso que estamos protegiendo, 00:06:53
cuando quiero utilizar la operación que nos dice el constructor semáforo, 00:07:02
abstractamente Wait, en realidad la clase semáforo se llama Acquire o la Try Acquire, 00:07:08
y en vez cuando queremos implementar la signal, la implementación de signal de la clase semáforo, 00:07:15
es la ReleaseSignal. 00:07:22
Entonces, cuando queremos implementar la release de la clase semáforo, 00:07:26
la implementación de signal de la clase semáforo es la Release. 00:07:33
Entonces, nosotros marcaremos un trozo de código poniendo un S.Acquire 00:07:38
antes del trozo de código que queremos mantener protegido, digamos así, en sección crítica, 00:07:44
y un S.Release cuando hemos acabado. 00:07:51
Entonces, vamos a ver un ejemplo. 00:07:57
Entonces, si os acordáis, este era el programa que no funcionaba. 00:08:02
Si yo lanzo este programa veo que, sustancialmente, lanzo dos threads, 00:08:09
estos threads lo que hacen es contar diez mil veces sumando uno a una variable, 00:08:14
y después de haber esperado que acaben, voy a mostrar esta variable. 00:08:21
Lo que me espero, como se hace diez mil veces, es que aquí haya veinte mil, 00:08:25
sin embargo, veo que cada vez que lo lanzo, el número aquí cambia, 00:08:29
y es otro valor que no se acerca nunca a veinte mil. 00:08:34
Entonces, ¿cómo puedo solucionar esto? 00:08:40
Sustancialmente, debería identificar cuál es la sección crítica en este caso. 00:08:43
La sección crítica es allá donde se está utilizando y tocando la variable X, 00:08:47
que es la variable que es compartida entre los varios threads que se están lanzando. 00:08:53
Entonces, sustancialmente, cuando yo hago esto, lo que debería hacer es marcar 00:08:59
que esta parte de aquí se tiene que ejecutar uno a la vez. 00:09:05
No puede haber dos threads que trabajen sobre todo en esta parte. 00:09:09
Esto, sustancialmente, como si fuera una operación atómica. 00:09:15
Cuando alguien empieza esta operación, a la acaba, hasta el final, 00:09:20
antes de poder dejar que otro thread lo haga. 00:09:24
Esto es, en un cierto sentido, nuestro recurso compartido, la posibilidad de sumar uno. 00:09:28
Entonces, ¿cómo hago esto con un semáforo? 00:09:34
Y tenemos esta cosa aquí. 00:09:38
Tenemos, además, que en nuestra X vamos a poner, 00:09:41
se puede hacer de varias formas, pero aquí para tenerlo sencillo, 00:09:46
un semáforo que será común. 00:09:49
Este semáforo será para todos los threads que yo voy a crear. 00:09:52
De hecho, tengo un constructor que recibe un semáforo 00:09:58
y este semáforo se pone aquí. 00:10:07
Se podría hacer de una forma más elegante que esta, pero para entendernos. 00:10:10
Luego, aquí está el main. 00:10:16
Lo que vamos a hacer con este main es crear un nuevo semáforo 00:10:19
y luego, al construir los threads, se pasa este semáforo. 00:10:23
Esto, en realidad, es una forma de hacerlo muy fea porque este es estático. 00:10:29
Entonces, quizás esto debería estar así. 00:10:35
Ahora sí que tiene más sentido que tenga un objeto, 00:10:42
que es para cada thread un objeto distinto, 00:10:50
pero como he creado un solo semáforo 00:10:54
y lo he pasado, el mismo semáforo, a los dos threads, 00:10:57
cuando los threads utilizarán esta cosa de aquí, 00:11:03
estarán utilizando el mismo semáforo, el mismo objeto. 00:11:06
Entonces, si este bloquea el semáforo, 00:11:10
este no podrá acceder porque el semáforo está bloqueado. 00:11:13
Tened en cuenta también que el semáforo aquí le he dado un 1, 00:11:18
entonces, en realidad, está actuando como un mutex. 00:11:22
Esto es implementación de un mutex. 00:11:25
Es decir, que en cada momento solo uno de los threads puede acceder al recurso compartido. 00:11:29
¿Dónde hago el recurso? 00:11:36
Aquí, dentro del for, este for lo haré 10.000 veces. 00:11:39
Lo que hago como primera cosa, intento bloquearme utilizando el sem.acquire. 00:11:44
Este acquire, fijaos que está en un try-catch con un interrupted exception 00:11:51
porque cuando yo lanzo sem.acquire me paro a la espera de que el recurso esté disponible. 00:11:58
Si ya está disponible, sigo adelante. 00:12:07
Si el recurso no está disponible, entonces me pararé aquí. 00:12:09
Y podría ser que alguien me lance una interrupción 00:12:13
y me pueda interrumpir como cuando hacía la slip o la join. 00:12:16
Si yo salgo de este punto de aquí, es porque el acquire ha funcionado 00:12:21
y tengo ahora mismo el derecho de poder modificar el recurso compartido. 00:12:28
Entonces lo modifico. Cuidado que ahora nadie puede hacer esta operación mientras yo estoy aquí. 00:12:37
Después, como ya he utilizado y no lo tengo que utilizar más, lanzo una sem.release 00:12:45
que sustancialmente desbloquea la posibilidad de acceder aquí a los otros threads, 00:12:54
al otro thread en este caso que estaba conectado. 00:13:00
Entonces, independientemente de quién sea el que accede, si él tenga el thread 1 o el thread 2, 00:13:03
en cada momento sólo podrá ejecutar esta operación quien ha pasado este bloqueo, digamos, este sem.acquire, 00:13:09
quien habrá encontrado el semáforo con valor 1, habrá cambiado el semáforo a 0 y habrá podido acceder aquí, 00:13:19
mientras el otro que habrá encontrado el semáforo ya a 0 tendrá que esperar hasta que se haga una release, 00:13:28
se vuelva a estar a 1 y entonces puede entrar. 00:13:36
Entonces, de esta forma, esta operación se hace en mutua exclusión, o sea que o uno o el otro. 00:13:39
Entonces, si ahora ejecuto este ejercicio, veo que el resultado ahora es el 20.000 que me esperaba desde el principio. 00:13:46
Así. 00:13:57
Vale, vamos a ver otros ejemplos de uso de semáforo. 00:14:00
Por ejemplo, podemos crear una pequeña cola de login, digamos así, ¿vale? 00:14:05
O sea que lo que queremos hacer con esto es que limitar el acceso a un determinado sistema. 00:14:15
Imaginemos que nosotros tenemos un sistema y que queremos que en un determinado momento solo n threads puedan acceder 00:14:22
y puedan trabajar con este sistema, es decir, n usuarios, ¿vale? 00:14:31
Con cada usuario asociado a un thread, pues entonces nos creamos esta pequeña clase que es la que gestiona el login, ¿vale? 00:14:34
Entonces tenemos un semáforo. Cuando creo un login, le pongo el valor máximo de cuántos usuarios o cuántos threads pueden acceder, ¿vale? 00:14:44
Y esto será el valor que pongo en el semáforo. 00:14:58
Luego nos creamos esta TryLogin, que sustancialmente es una operación para intentar logearse. 00:15:02
Si ésta devuelve un true, entonces he podido logearme porque había todavía espacios, y en vez ésta me devuelve false, 00:15:10
quiere decir que he intentado logearme pero no lo he conseguido. 00:15:18
Fijaos que lo que hace es simplemente esto. 00:15:22
Esta operación de aquí es un poquito distinta de la que hemos visto antes, que era la Acquire. 00:15:26
Esta de aquí es bloqueante, en el sentido que se en punto Acquire o adquiere el acceso, entonces entra dentro y sigue adelante y luego hace la release, 00:15:33
o se bloquea aquí, ¿vale? Y se queda bloqueado hasta que alguien haga la release. 00:15:44
Cuidado también con la gestión de la release, porque si nadie hace la release, éste estará bloqueado aquí para siempre hasta que alguien no lo interrumpa, 00:15:50
o si no, se queda ahí. 00:15:59
Sin embargo la Try Acquire no es bloqueante, lo intenta. 00:16:02
Si devuelve true, quiere decir que he podido acceder al recurso y ahora es mío, entonces lo puedo utilizar. 00:16:07
Si en vez de no lo consigue devuelve un false, que puedo utilizar para hacer otra cosa, para no bloquearme y decirme, a ver, si me ha dado un false, pues haz otra cosa. 00:16:17
Está la Logout, que sustancialmente hace una release sobre el semáforo, entonces aumenta el número que le he pasado. 00:16:26
Y luego está la Avalo Slot, que me dice cuántos, sustancialmente cuántos es el valor actual del semáforo. 00:16:35
Si vemos que yo le he puesto un 5 originalmente, se ha hecho 2 Try Login, y entonces ahora había solo 3 posibles nuevos usuarios que se pueden conectar, pues esto me devolvería 3. 00:16:46
Entonces esto seta el límite máximo, esto reduce hasta 0, que sustancialmente no permitiría acceder a nadie más, y este de aquí, en vez de decir que un usuario ha hecho Logout, entonces pueden acceder a otro. 00:17:01
Y esta es una forma de hacer esto, lo que vamos a hacer aquí es utilizarlo. Tengo aquí un Thread, que sustancialmente tiene dentro un Log. 00:17:17
Lo que hemos hecho es un constructor que asocia el objeto Login, L al Log del Thread, y luego un Main que sustancialmente lanza 10 Thread Login, 10 de estos que ahora veremos que hacen Threads. 00:17:33
Pero al principio crea un objeto Login, de tipo Login, con máximo 3. Esto quiere decir que en cada momento solo 3 de estos 10 Threads podrán estar activos, los otros no, porque no podrán acceder al semáforo. 00:17:59
¿Qué hace un Thread de tipo Login Main? Pues sustancialmente intenta logearse. Si lo consigue, entonces esto de aquí sería True, y por lo tanto este de aquí sería False, y por lo tanto iría aquí, diciendo que está esperando. 00:18:17
Si en vez no lo consigue, esto sería False, por lo tanto Not, este de aquí, sería True, entraría aquí dentro, lo que haría es el hecho que escriba que está esperando, escriba quién es y qué está esperando, y después que espere un segundo antes de reintentarlo otra vez. 00:18:43
El momento en que ha entrado, lo que hace aquí mismo es que sustancialmente ha adquirido el acceso al recurso y por lo tanto puede trabajar, entonces si le he hecho que sustancialmente por 10 veces espere un tiempo aleatorio entre 0 y 1000, que sustancialmente es haz una operación, escribe que estás trabajando y vuelve a hacerlo por 10 veces. 00:19:05
Cuando he acabado, entonces ya no necesito el recurso, ya no necesito trabajar aquí, lo que hago es un Logout y acabo. 00:19:36
Entonces la idea es que sustancialmente partirán los 10, solo 3 de ellos empezarán a trabajar, mientras los demás estarán esperando, y después cuando los primeros 3 han acabado, a medida que los 3 van acabando, pues haciendo el Logout permitirán a otro 3 de entrar. 00:19:46
Vamos a ejecutarlo. 00:20:09
Veis que ha entrado el 3 de 0, los 3 de 6, 5, 3 están esperando, los 3 de 2 y 1 han entrado, fijaos que esto es no determinista en el sentido de que parece casi que éste ha entrado mientras que éste está esperando y que sea después, 00:20:10
pero os recuerdo que la escritura aquí podría ser mezclada, o sea, claramente lo que ha pasado es que los que han conseguido entrar son el 0, el 1 y el 2, 0 y 1 y 2 son los primeros 3 que han llegado aquí y han podido entrar. 00:20:39
Si lanzara esto muchas veces, pues es posible que alguna vez el 0, el 1 y el 2 no puedan más, que sean otros. 00:20:58
Y los otros esperan. Cuando los 3 empiezan a escribir, fijaos que como el tiempo que tienen que esperar aquí dentro es aleatorio, pues entonces, por ejemplo, aquí el 3 de 1 ha conseguido hacer 3 operaciones mientras que el 2 ha hecho una sola. 00:21:05
Entonces van así, así, así, hasta que lleguen a alguien que llega a la décima iteración. Entonces cuando hace esto, el 3 de 2 ha acabado y por lo tanto me espero que alguien pueda entrar. 00:21:25
Y por ejemplo, ha sido despertado el 3 de 4 y ha sido el 3 de 4 el que ha entrado. Fijaos que el 3 de 3 no lo ha conseguido. También han acabado el 1 y el 0 aquí y entonces veo que entra también el número 6, el número 5 y ahora a trabajar son el 5, el 6 y el 4. 00:21:40
¿Veis? Mientras los demás están esperando, esperando, esperando. Y así, así, así, así. En cada momento yo tengo solo 3 threads que están trabajando mientras todos los demás están esperando. 00:22:02
¿Vale? Entonces este es un ejemplo, por ejemplo, de semáforo donde el semáforo no es un mutex, ¿vale? Este semáforo me dice cuántos recursos que sería la posibilidad de ejecutarse. 00:22:14
En este caso están, están disponibles. Por lo tanto, si yo cambiara esto a 5, pues entonces 5, máximo 5 de estos podrían ejecutarse en paralelo. 00:22:32
¿Vale? Y luego estaría un ejercicio, ¿vale? Os digo el enunciado del ejercicio, luego podéis parar este vídeo, hacerlo y luego vemos una posible solución, ¿vale? 00:22:45
Entonces el ejercicio de semáforos sería sustancialmente crear dos clases, la clase Generador y la clase Worker. ¿Qué hace la clase Worker? La clase Worker es una clase sencilla, ¿vale? 00:23:03
Tiene una variable tipo string que contiene un número, ¿sí? El worker no hace nada, está allí y espera, duerme. Si alguien lanza un interrupt sobre este thread, entonces se despierta y escribe el número 00:23:18
contenido en su variable, en esta variable de aquí, multiplicado por 1000. Entonces sustancialmente alguien tiene que haberle metido a un worker la string, o sea, el valor dentro de su variable, después le lanza un interrupt, 00:23:34
él se despierta, hace el trabajo, que es multiplicar por 1000 esta variable, y vuelve a dormir, ¿vale? La clase Generator es la clase que se encarga de generar un número y luego este número lo pasa al worker y lo interrumpe, ¿vale? 00:23:51
Entonces esto es, digamos, un cliente que pide un servicio a este worker. El worker es el que hace el trabajo, este necesita procesar datos, crea el dato que tiene que procesar, se lo pasa al worker, el worker es el que lo procesa. 00:24:06
En el main queremos crear un cierto número de worker, pongamos 5, y un número mucho mayor de generadores, o sea, hay pocos servidores, pocos trabajadores y muchos que generan los números para procesar, ¿vale? 00:24:20
Por ejemplo, si este es 5, pues esto puede ser 50. A este punto usamos semáforos en Generator, ¿vale? Para que en cada momento como máximo haya max generadores que puedan acceder a un worker, porque mis recursos compartidos son max, ¿vale? 00:24:38
Si tengo 5 worker no puedo permitir que 10 generadores accedan a la variable compartida de max, de worker, ¿vale? Además, quiero garantizar que cuando un generador puede acceder a un worker, entonces tengo el semáforo que me dice puede acceder, pues le sea asignado un worker concreto y que él use ese worker para poder hacer el trabajo que quiere hacer, ¿vale? 00:24:56
Entonces el generador seguirá allí generando números. Los generadores compiten para acceder a un worker y cuando se le asigna un worker, pues lo usa, ¿vale? 00:25:23
Entonces, intentad hacer vosotros este ejercicio, ¿vale? Y yo sigo con la corrección, pero no miréis la corrección antes de no haberlo intentado vosotros. 00:25:35
Entonces, este ejercicio, aquí tenemos la clase worker, la clase worker es sencilla, tiene la variable s, que es sustancialmente el número que luego utilizará, y luego me he creado una variable booleana que es el trabajando, ¿vale? 00:25:48
Esto me sirve para saber si un worker en un determinado momento está disponible o no para ser ejecutado, porque por un lado tendré algo que me diga que máximo 5 generadores pueden acceder a 5 workers, pero tengo que saber cuáles de los workers están trabajando ya y cuáles no, ¿vale? 00:26:09
Entonces, el worker es un thread, ¿vale? Entonces lo puedo crear con un nombre, después el run es que siempre, para siempre, o el true, lo que hace es esperar un mogollón de tiempo, ¿vale? Cada vez que acabe esto, pues volverá a hacerlo y hacerlo y hacerlo, porque sustancialmente, sin paréntesis, es esto lo que ejecuta, ¿vale? 00:26:29
Ahora, cuando me interrumpe, si me interrumpe, entonces accedo a esta parte de aquí y luego vuelvo otra vez a dormir, ¿vale? 00:26:56
Entonces, ¿qué hace esta parte de aquí? Pues yo simplemente pillo el valor que me han pasado en mi string as, lo multiplico por 1000 y lo imprimo en pantalla diciendo quién soy, ¿vale? 00:27:07
Teniendo en cuenta también que este working es false, ¿vale? Entonces, cuando alguien me despertará, entonces me habrá aceptado que este es true para que nadie más me use y cuando yo he acabado el trabajo, vuelvo a poner que soy falso, ¿vale? 00:27:19
Si esto está false, quiere decir que se puede acceder a este working, si este está true, quiere decir que está trabajando y nadie me puede parar. Fijaos que aquí no se pone nunca en true, ¿vale? 00:27:41
Porque lo que yo quiero es que se pueda acceder y se pueda poner en true sólo si alguien ha adquirido la posibilidad de trabajar con este working, ¿vale? 00:27:57
Vamos a ver el generador. El generador tiene dos semáforos. Tiene un semáforo que gestiona cuántos generadores pueden acceder, entonces será un semáforo que tendrá un valor, por ejemplo, de más trabajadores, seis o nosotros habíamos dicho cinco, 00:28:11
que son al máximo, quiero cinco trabajadores, tengo cinco trabajadores, entonces máximo cinco generadores pueden acceder a la vez a los recursos y también otro semáforo, ¿vale? 00:28:30
Que en vez es el que me garantiza que en un determinado momento, cuando yo he accedido a la posibilidad de acceder a uno de los cinco recursos, pues tengo que elegir cuál de los cinco es de verdad el que voy a utilizar 00:28:49
y esta operación tiene que estar hecha en mutua exclusión porque si no, dos threads podrían elegir el mismo working. Fijaos que dos threads de generador han entrado a la vez en los cinco generadores que pueden trabajar paralelamente, 00:29:04
si los dos llegan al primer working y lo encuentran libre, pues los dos podrían intentar utilizar el primer working, entonces sería un problema. Entonces uso otro semáforo que sea en mutua exclusión para que, sustancialmente, 00:29:21
este segundo semáforo me impida, una vez que haya entrado, pues la elección de cuáles de los cinco workers es el que voy a utilizar yo se hace exclusivamente, o sea que lo hacen de uno en uno. 00:29:36
Una vez que yo sé cuál es el thread que tengo que utilizar, lo marcaré como working igual a true y, por lo tanto, no está libre y ya podré dejar de, o sea, soltar este semáforo porque, sustancialmente, 00:29:50
si otro intenta pillarlo, lo encontrará ocupado. Este es el número de trabajadores que tengo, tengo una red de trabajadores que lanzo, tengo un número que me dice el último worker ocupado, 00:30:07
es decir, cuando hemos dicho que yo voy a mirar de los cinco cuáles de los cinco workers voy a utilizar, pues me apunto cuál es el worker que he utilizado. De esta forma intento evitar la inanición, 00:30:26
intento evitar de mirar este array siempre desde la posición cero hasta la cuatro, sino que cada vez empezaré desde una posición distinta, desde la última posición que he utilizado. 00:30:41
Y luego cuántos generadores quiero crear, en este caso 50. Vamos a ver antes el main, el main escribe inicio, luego crea un semáforo con max trabajadores y un semáforo con uno solo, 00:30:56
este es un mutex, mientras este de aquí es el que me dice cuántos trabajadores hay disponibles, y luego creo un array worker donde dentro pongo el número máximo de worker que hemos dicho. 00:31:18
A este punto lanzo los worker, para lanzar los worker hago un for que me hará esto el número max trabajadores de veces y me pone en posición i un nuevo worker que se llamará worker work y un numerito, 00:31:31
después hará un cierto número de veces el lanzar generadores, fijaos que los generadores tienen asociados y pasados los dos semáforos que hemos visto antes, siendo el primero el que me gestiona cuántos trabajadores hay 00:31:48
y el segundo el que me gestionará cuál es cuál es el trabajador que yo quiero. Vamos a ver el constructor y efectivamente lo que hace es el primero lo asocia a este de aquí que será el que gestiona cuántos worker hay, 00:32:11
mientras el segundo este de aquí me gestiona cuál de los max trabajadores voy a utilizar concretamente. Entonces veamos el run, este run es un run de un generador, ¿qué hace un generador en general? 00:32:29
Pues crea una variable myrec que es sustancialmente el trabajador que va a utilizar y empieza para siempre a hacer lo siguiente, se hace un número num que es 0 y luego intenta acceder a uno de los 5 en nuestro caso trabajadores que hay. 00:32:47
Si no consigue acceder a ninguno de los 5 trabajadores se quedará aquí esperando, cuando sem.release se despertarán los threads que estaban en este punto e intentarán competir entre ellos para ver si consiguen adquirir una cosa. 00:33:16
Entonces a partir de este punto del código aquí al máximo puede haber 5 threads que trabajen, de los 50 sólo 5 conseguirán pasar esta primera barrera y aquí vamos a pedir un sem.counter.query. 00:33:37
Esta segunda barrera trabaja con el mutex entonces de estos 5 que han entrado aquí sólo uno podrá acceder aquí dentro y este de aquí va de aquí hasta aquí. 00:33:56
En esta parte de aquí esta sección crítica aquí se hace de uno en uno, todo esto hasta el sem.release que está aquí todo esto se ejecuta de 5 en 5. 00:34:11
5 threads pueden trabajar aquí dentro en cada momento pero esta parte de aquí sólo la puede ejecutar uno a la vez, fijaos que hay esta parte de aquí que sustancialmente no está en mutex, esta se puede hacer. 00:34:32
Entonces hay 5 workers, sólo 5 threads acceden aquí, cada uno por separado de uno en uno lo que van a hacer es asociar, pillarse un número sustancialmente, este número es el número que se pasará al worker, 00:34:53
que yo he hecho un número incremental que empieza en cero y que luego cada vez que se utiliza se suma uno para que haya números distintos, lo veremos ahora, y luego pillo el último worker ocupado y le añado uno, 00:35:10
porque si el último ocupado, el último que había elegido anteriormente era el 3 por ejemplo, ahora pillo el 4, modulo el número máximo de trabajadores, 00:35:25
es decir que si yo he pillado el 4 y el 4 era la última posición posible pues haría 4 más 1, 5, modulo 4 vuelve a ser cero y por lo tanto volvería aquí abajo, modulo 5. 00:35:34
Entonces a este punto que he hecho estas dos cosas lo que hago es mientras workers en posición esta de aquí que acabo de actualizar está trabajando, es decir mientras que esto sea true, 00:35:50
yo me lo salto, lo que hago es sumar uno, rehacer sustancialmente esta operación, ir a mirar otro worker, porque este worker ya está trabajando entonces no lo voy a seleccionar, 00:36:11
y lo digo, por si acaso entrar aquí dentro digo estoy buscando un worker, ahora el hecho es que cuando salga de aquí y esté después de este while, 00:36:26
quiere decir que he encontrado un worker cuyo último worker ocupado, este valor de aquí, me da que está libre, su working está aceptado a false, 00:36:40
en el sentido que como yo esta cosa aquí la estoy haciendo mientras que esto sea true, si salgo de aquí quiere decir que el que he encontrado es false, 00:36:58
y asigno que este último worker ocupado que he encontrado que ahora está libre es el mío, es el que voy a utilizar, entonces este generador ahora utilizará, 00:37:07
pongamos que aquí haya dado 3, pues el worker 3 para trabajar, entonces que hacemos aquí, pues aceptamos que este el worker en posición mirec que es este de aquí, 00:37:22
pues punto working es true, fijaos que esto lo hago dentro de esta parte de aquí, esto quiere decir que toda esta operación se ha hecho de uno en uno, 00:37:37
no puede haber dos threads que han hecho esta operación a la vez y por lo tanto sólo una persona puede escribir aquí, sólo un thread ha podido escribir true aquí dentro, 00:37:48
no puede haber dos threads que a la vez han escrito true, la única otra forma que hay para cambiar esta variable es esto, que se ejecuta sólo si él está trabajando, 00:37:58
para que se ejecute quiere decir que alguien lo ha despertado de aquí, pero es el generador que lo despierta, entonces si el generador lo ha despertado quiere decir que antes ya le ha puesto el true, 00:38:15
y nadie más en ese momento podría haberlo hecho, así, entonces una vez que he dejado esto puede ser que otro de los 5 que estaban esperando aquí, 00:38:27
bueno de los otros 4, pues entra aquí dentro y empiece a hacer este trabajo y que encuentre otro distinto worker para trabajar con él, sin embargo el que había salido ahora de esta sección crítica interna, 00:38:41
pues lo que hace sigue en la sección crítica externa la de que tiene 5 y lo que hace es pedir al worker este de aquí que procese el número, este número de aquí que había pillado aquí, 00:38:55
y entonces lo que hace es meter en ese este variable aquí y luego después interrumpirlo, fijaos que esto sólo un thread lo puede hacer, sólo un thread que había entrado aquí y que ha pillado mayrec como esto lo puede hacer, 00:39:11
porque todos los demás que puedan entrar aquí lo encontrarán como ocupado, será sólo el worker que después de haber hecho el trabajo se aceptará false y permitirá a otro generador que estaba buscando de encontrarlo como libre, 00:39:33
y entonces a este punto puede hacer la release y se queda esperando un rato para dejar que otros puedan entrar y este punto vuelve a jugar, es decir vuelve aquí a intentar adquirir uno de los 5 recursos, 00:39:54
entonces esto lo que hace es algo parecido a esta cosa aquí, lo paro porque esto seguiría al infinito, vamos a mirar que está pasando, entonces se inicia el thread 0 por ejemplo pide al worker 1 de procesar el 0 y veis que el worker 1 procesa el 0, 0 por 1000, 0, 00:40:10
luego el thread 4 pide al worker 2 de procesar el 1, entonces aquí tengo el 1000, el thread 5 pide al worker 3 el 2000, el 1 fijaos que el orden es casual, pide al worker 4 el 3000, 00:40:35
a este punto el thread 6 pide al worker 0, el thread 10 pide al worker 1, fijaos que hay una cierta 0, 1, 2, 3, 4 por esta razón de aquí, que yo cada vez sumo uno al último ocupado y empiezo a mirar si está libre desde el siguiente, 00:41:02
entonces con lo siguiente normalmente está libre pues lo encuentro y lo puedo trabajar, fijaos que sigo adelante, aquí hay threads que están buscando un worker y que no lo encuentran libre, 00:41:21
entonces están esperando hasta que el thread se ponga libre y entonces trabajan, esto será el 4 probablemente, dando vueltas hasta aquí que ha encontrado una forma de salir, 00:41:37
que será el 3 probablemente, puede ser que sea el 3 que haya entrado aquí dentro y que no haya encontrado uno libre hasta entonces, fijaos que estos son también milisegundos donde se hace 00:42:03
un montón de veces esto porque aquí este while está aquí dentro entonces estaba dando vueltas ahí encontrándolos ocupados porque probablemente estaban todavía haciendo estas operaciones de otro thread 00:42:20
y por lo tanto cuando luego se han puesto a falso y han vuelto aquí pues entonces sí que han sido interrumpidos otra vez, más o menos, entonces esto, si no lo habéis hecho anteriormente pues intentad volver a hacerlo, 00:42:36
las cosas importantes de este ejercicio lo que hay que pensar bien es dónde pongo los semáforos, para qué me sirven los semáforos y dónde están las partes compartidas que tengo que bloquear y ejecutar, 00:42:54
fijaos que no todos los semáforos pueden tener valor en la misma parte, en la misma sección crítica. Y ya está. 00:43:08
Última cosa, estos son los semáforos, hemos visto tantos semáforos mutex como el otro y ahora tanto para dar una pincelada en lo que haremos la próxima vez, 00:43:22
este de aquí es otra vez el ejercicio del Somar 1 donde pero se utiliza lo que se llama un monitor, esta cosa de aquí es una sincronización, me está diciendo que esta operación de aquí sólo se puede hacer un thread a la vez, 00:43:39
entonces esto es igual a hacer un semáforo pero más alto nivel, esto es lo que se llama la segunda forma de hacer las secciones críticas que es los monitores, sería esto. 00:44:01
¿Dónde están? Lo encontraré, esto no va a salir aquí. Semáforos lo hemos visto, los monitores la próxima vez que veamos. Es una forma un poquito más de alto nivel, 00:44:15
me esconden cuando se hace la release, cuando se hace la acquire y simplemente yo lo que tengo que hacer es marcar cuál es la zona que quiero que se ejecute separadamente, digamos en exclusión, 00:44:33
y él me lo hace, ¿vale? Pero con los monitores lo que no puede hacer es un semáforo del tipo de que tenga un valor más grande, ¿vale? Una cosa parecida a esta. Esto no se puede hacer, ¿vale? Con los monitores sólo puede hacer mutex. Eso es fácilmente. Y ya está. 00:44:55
Idioma/s:
es
Autor/es:
Stefano Chiesa
Subido por:
Stefano C.
Licencia:
Reconocimiento - No comercial
Visualizaciones:
54
Fecha:
15 de noviembre de 2022 - 20:42
Visibilidad:
Clave
Centro:
IES ROSA CHACEL
Duración:
45′ 18″
Relación de aspecto:
1.78:1
Resolución:
1920x1080 píxeles
Tamaño:
540.79 MBytes

Del mismo autor…

Ver más del mismo autor


EducaMadrid, Plataforma Educativa de la Comunidad de Madrid

Plataforma Educativa EducaMadrid