0 00:00:00,000 --> 00:00:12,000 Si os acordáis, la última vez hablamos de sección crítica, que es la parte del programa que tiene que estar protegida. 1 00:00:12,000 --> 00:00:23,000 Sería la parte donde puedan surgir algunos de los problemas que hemos visto en clase, como la inconsistencia de memoria o la discondition. 2 00:00:23,000 --> 00:00:39,000 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. 3 00:00:39,000 --> 00:00:54,000 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. 4 00:00:54,000 --> 00:01:05,000 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, 5 00:01:05,000 --> 00:01:12,000 que son estas tres, entonces lo que tenemos que hacer es marcarlo como sección crítica. 6 00:01:12,000 --> 00:01:18,000 Para marcar la sección crítica hay dos formas sencillas de hacerlo. 7 00:01:18,000 --> 00:01:23,000 Una son los semáforos, que son un poquito más a bajo nivel, y luego están los monitores. 8 00:01:23,000 --> 00:01:26,000 En este vídeo vamos a ver los semáforos. 9 00:01:26,000 --> 00:01:37,000 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, 10 00:01:37,000 --> 00:01:43,000 cuando el semáforo se pone rojo tú te tienes que parar y esperar a que sea tu turno para poder utilizarlo. 11 00:01:44,000 --> 00:01:54,000 Un semáforo se representa con una variable entera, que representa el número de instancias que tenga a disposición. 12 00:01:54,000 --> 00:02:02,000 En general, yo podría tener varios recursos compartidos y poder, por ejemplo, tres impresoras, 13 00:02:02,000 --> 00:02:10,000 y entonces mientras que tres threads quedan a utilizar la impresora, hasta tres hay posibilidad de utilizarla. 14 00:02:10,000 --> 00:02:14,000 Cuando llegaría el cuarto thread que quiere utilizar uno de estos recursos, 15 00:02:14,000 --> 00:02:21,000 pues entonces se debería bloquear esperando a que una de las tres impresoras se libere en un cierto sentido. 16 00:02:21,000 --> 00:02:26,000 Entonces en la fase de inicialización de los semáforos se le pone un valor inicial, 17 00:02:26,000 --> 00:02:30,000 que es el número de recursos disponibles en ese momento. 18 00:02:30,000 --> 00:02:38,000 Cuando alguien adquiere el control sobre uno de estos recursos, se restará uno hasta que el valor sea cero. 19 00:02:38,000 --> 00:02:46,000 Si el valor es cero, entonces el programa no permitirá seguir adelante y tendrá que bloquear el acceso a estos recursos. 20 00:02:46,000 --> 00:02:54,000 Cuando alguno de los threads ha usado el recurso y lo libera, pues entonces el número asociado con el semáforo subirá. 21 00:02:54,000 --> 00:03:04,000 Un hilo ejecuta una wait. Wait es una operación general, ¿vale? No la penséis en Java. 22 00:03:05,000 --> 00:03:11,000 En general cuando hablamos de semáforos hay dos operaciones, la wait y la signal. 23 00:03:11,000 --> 00:03:22,000 La wait sustancialmente es la que adquiere el elemento, adquiere el recurso compartido. 24 00:03:22,000 --> 00:03:27,000 Si el valor es menor que cero es que no hay estantes disponibles 25 00:03:27,000 --> 00:03:36,000 y podría ser utilizado este valor menor que cero para saber cuántos hilos están a la espera de un determinado recurso. 26 00:03:36,000 --> 00:03:42,000 El hilo se bloquea y queda a la espera de que se liberen. 27 00:03:42,000 --> 00:03:47,000 Cuando se liberen estos recursos se utiliza la operación signal, ¿vale? 28 00:03:47,000 --> 00:03:53,000 La operación signal es como señalar a todos lo que están esperando que ahora hay un recurso libre. 29 00:03:53,000 --> 00:04:00,000 Entonces lo que tendrán que hacer los threads que estaban esperando este recurso es competir entre ellos 30 00:04:00,000 --> 00:04:09,000 para poder sustancialmente adquirir el recurso ellos, volver a restar uno al valor del semáforo 31 00:04:09,000 --> 00:04:13,000 y por lo tanto todos los demás se mantendrán otra vez a la espera. 32 00:04:13,000 --> 00:04:16,000 La wait y la signal tienen que ser atómicas. 33 00:04:16,000 --> 00:04:23,000 Es decir, que tienen que ser operaciones que cuando lanzo el pillar el recurso o soltar el recurso 34 00:04:23,000 --> 00:04:28,000 se tienen que hacer todo seguido. 35 00:04:28,000 --> 00:04:32,000 No puedo pararme a mitad de una operación de wait o de signal. 36 00:04:32,000 --> 00:04:39,000 Y hay soluciones de distintos tipos, tanto hardware como software, para poder implementar realmente estas cosas 37 00:04:39,000 --> 00:04:42,000 como test and setup, swap o algoritmo de aplicación. 38 00:04:42,000 --> 00:04:46,000 Que nosotros son bien interesantes pero no vamos a profundizar en estos. 39 00:04:46,000 --> 00:04:53,000 Lo que sí vamos a ver es que Java nos proporciona una clase semáforo en el paquete java.util.concurrent 40 00:04:53,000 --> 00:04:56,000 que implementa todo lo que necesitamos. 41 00:04:56,000 --> 00:05:00,000 En particular implementa el equivalente de una wait y de una signal. 42 00:05:00,000 --> 00:05:05,000 Y nosotros podemos utilizarlo para marcar nuestras secciones críticas. 43 00:05:05,000 --> 00:05:13,000 Cuando un semáforo es binario, es decir, que cuando el valor que le damos al semáforo aquí 44 00:05:13,000 --> 00:05:19,000 que se presenta con una variable entera es 1, entonces hay un solo recurso 45 00:05:19,000 --> 00:05:23,000 se está implementando un tipo de semáforo especial que es el mutex. 46 00:05:24,000 --> 00:05:31,000 Sustancialmente quiere decir que solo un thread puede en un determinado momento acceder a ese recurso. 47 00:05:31,000 --> 00:05:36,000 Porque si está a 1 el recurso está disponible, si está a 0 el recurso no está disponible. 48 00:05:36,000 --> 00:05:42,000 Y es, sustancialmente estamos haciendo lo que es una mutuexclusión. 49 00:05:42,000 --> 00:05:48,000 Es decir, que solo un thread puede en un determinado momento acceder a ese recurso. 50 00:05:48,000 --> 00:05:54,000 Esto por ejemplo nos puede servir cuando utilizamos el acceso a una variable, una variable compartida 51 00:05:54,000 --> 00:05:56,000 y tenemos que escribir en esa variable. 52 00:05:56,000 --> 00:06:00,000 Pues como en cada momento puede escribir solo un thread en esa variable, 53 00:06:00,000 --> 00:06:06,000 pues entonces creamos un semáforo con valor 1 para que si yo adquiero el semáforo, 54 00:06:06,000 --> 00:06:10,000 adquiero la posibilidad de utilizar el semáforo en un determinado momento. 55 00:06:11,000 --> 00:06:20,000 De esta forma evito que dos threads intenten escribir a la vez en la misma variable o en el mismo objeto. 56 00:06:25,000 --> 00:06:29,000 Entonces vamos a hacer un ejemplo. 57 00:06:29,000 --> 00:06:36,000 Entonces vamos a ver cómo se puede utilizar ejemplos de semáforos en mutex. 58 00:06:36,000 --> 00:06:44,000 Por ejemplo, si os acordáis el ejercicio que sumaba 10.000 veces 1 a una variable 59 00:06:44,000 --> 00:06:48,000 y habíamos visto que generaba posibles problemas, 60 00:06:48,000 --> 00:06:53,000 pues antes de empezar a ver una posible solución, tenemos que saber que la clase semáforo, 61 00:06:53,000 --> 00:07:02,000 que tiene un constructor semáforo con un entero que es el número de instancias libres de recurso que estamos protegiendo, 62 00:07:02,000 --> 00:07:08,000 cuando quiero utilizar la operación que nos dice el constructor semáforo, 63 00:07:08,000 --> 00:07:15,000 abstractamente Wait, en realidad la clase semáforo se llama Acquire o la Try Acquire, 64 00:07:15,000 --> 00:07:22,000 y en vez cuando queremos implementar la signal, la implementación de signal de la clase semáforo, 65 00:07:22,000 --> 00:07:26,000 es la ReleaseSignal. 66 00:07:26,000 --> 00:07:33,000 Entonces, cuando queremos implementar la release de la clase semáforo, 67 00:07:33,000 --> 00:07:38,000 la implementación de signal de la clase semáforo es la Release. 68 00:07:38,000 --> 00:07:44,000 Entonces, nosotros marcaremos un trozo de código poniendo un S.Acquire 69 00:07:44,000 --> 00:07:51,000 antes del trozo de código que queremos mantener protegido, digamos así, en sección crítica, 70 00:07:51,000 --> 00:07:57,000 y un S.Release cuando hemos acabado. 71 00:07:57,000 --> 00:08:02,000 Entonces, vamos a ver un ejemplo. 72 00:08:02,000 --> 00:08:09,000 Entonces, si os acordáis, este era el programa que no funcionaba. 73 00:08:09,000 --> 00:08:14,000 Si yo lanzo este programa veo que, sustancialmente, lanzo dos threads, 74 00:08:14,000 --> 00:08:21,000 estos threads lo que hacen es contar diez mil veces sumando uno a una variable, 75 00:08:21,000 --> 00:08:25,000 y después de haber esperado que acaben, voy a mostrar esta variable. 76 00:08:25,000 --> 00:08:29,000 Lo que me espero, como se hace diez mil veces, es que aquí haya veinte mil, 77 00:08:29,000 --> 00:08:34,000 sin embargo, veo que cada vez que lo lanzo, el número aquí cambia, 78 00:08:34,000 --> 00:08:40,000 y es otro valor que no se acerca nunca a veinte mil. 79 00:08:40,000 --> 00:08:43,000 Entonces, ¿cómo puedo solucionar esto? 80 00:08:43,000 --> 00:08:47,000 Sustancialmente, debería identificar cuál es la sección crítica en este caso. 81 00:08:47,000 --> 00:08:53,000 La sección crítica es allá donde se está utilizando y tocando la variable X, 82 00:08:53,000 --> 00:08:59,000 que es la variable que es compartida entre los varios threads que se están lanzando. 83 00:08:59,000 --> 00:09:05,000 Entonces, sustancialmente, cuando yo hago esto, lo que debería hacer es marcar 84 00:09:05,000 --> 00:09:09,000 que esta parte de aquí se tiene que ejecutar uno a la vez. 85 00:09:09,000 --> 00:09:15,000 No puede haber dos threads que trabajen sobre todo en esta parte. 86 00:09:15,000 --> 00:09:20,000 Esto, sustancialmente, como si fuera una operación atómica. 87 00:09:20,000 --> 00:09:24,000 Cuando alguien empieza esta operación, a la acaba, hasta el final, 88 00:09:24,000 --> 00:09:28,000 antes de poder dejar que otro thread lo haga. 89 00:09:28,000 --> 00:09:34,000 Esto es, en un cierto sentido, nuestro recurso compartido, la posibilidad de sumar uno. 90 00:09:34,000 --> 00:09:38,000 Entonces, ¿cómo hago esto con un semáforo? 91 00:09:38,000 --> 00:09:41,000 Y tenemos esta cosa aquí. 92 00:09:41,000 --> 00:09:46,000 Tenemos, además, que en nuestra X vamos a poner, 93 00:09:46,000 --> 00:09:49,000 se puede hacer de varias formas, pero aquí para tenerlo sencillo, 94 00:09:49,000 --> 00:09:52,000 un semáforo que será común. 95 00:09:52,000 --> 00:09:58,000 Este semáforo será para todos los threads que yo voy a crear. 96 00:09:58,000 --> 00:10:07,000 De hecho, tengo un constructor que recibe un semáforo 97 00:10:07,000 --> 00:10:10,000 y este semáforo se pone aquí. 98 00:10:10,000 --> 00:10:16,000 Se podría hacer de una forma más elegante que esta, pero para entendernos. 99 00:10:16,000 --> 00:10:19,000 Luego, aquí está el main. 100 00:10:19,000 --> 00:10:23,000 Lo que vamos a hacer con este main es crear un nuevo semáforo 101 00:10:23,000 --> 00:10:29,000 y luego, al construir los threads, se pasa este semáforo. 102 00:10:29,000 --> 00:10:35,000 Esto, en realidad, es una forma de hacerlo muy fea porque este es estático. 103 00:10:35,000 --> 00:10:42,000 Entonces, quizás esto debería estar así. 104 00:10:42,000 --> 00:10:50,000 Ahora sí que tiene más sentido que tenga un objeto, 105 00:10:50,000 --> 00:10:54,000 que es para cada thread un objeto distinto, 106 00:10:54,000 --> 00:10:57,000 pero como he creado un solo semáforo 107 00:10:57,000 --> 00:11:03,000 y lo he pasado, el mismo semáforo, a los dos threads, 108 00:11:03,000 --> 00:11:06,000 cuando los threads utilizarán esta cosa de aquí, 109 00:11:06,000 --> 00:11:10,000 estarán utilizando el mismo semáforo, el mismo objeto. 110 00:11:10,000 --> 00:11:13,000 Entonces, si este bloquea el semáforo, 111 00:11:13,000 --> 00:11:18,000 este no podrá acceder porque el semáforo está bloqueado. 112 00:11:18,000 --> 00:11:22,000 Tened en cuenta también que el semáforo aquí le he dado un 1, 113 00:11:22,000 --> 00:11:25,000 entonces, en realidad, está actuando como un mutex. 114 00:11:25,000 --> 00:11:29,000 Esto es implementación de un mutex. 115 00:11:29,000 --> 00:11:36,000 Es decir, que en cada momento solo uno de los threads puede acceder al recurso compartido. 116 00:11:36,000 --> 00:11:39,000 ¿Dónde hago el recurso? 117 00:11:39,000 --> 00:11:44,000 Aquí, dentro del for, este for lo haré 10.000 veces. 118 00:11:44,000 --> 00:11:51,000 Lo que hago como primera cosa, intento bloquearme utilizando el sem.acquire. 119 00:11:51,000 --> 00:11:58,000 Este acquire, fijaos que está en un try-catch con un interrupted exception 120 00:11:58,000 --> 00:12:07,000 porque cuando yo lanzo sem.acquire me paro a la espera de que el recurso esté disponible. 121 00:12:07,000 --> 00:12:09,000 Si ya está disponible, sigo adelante. 122 00:12:09,000 --> 00:12:13,000 Si el recurso no está disponible, entonces me pararé aquí. 123 00:12:13,000 --> 00:12:16,000 Y podría ser que alguien me lance una interrupción 124 00:12:16,000 --> 00:12:20,000 y me pueda interrumpir como cuando hacía la slip o la join. 125 00:12:21,000 --> 00:12:28,000 Si yo salgo de este punto de aquí, es porque el acquire ha funcionado 126 00:12:28,000 --> 00:12:37,000 y tengo ahora mismo el derecho de poder modificar el recurso compartido. 127 00:12:37,000 --> 00:12:45,000 Entonces lo modifico. Cuidado que ahora nadie puede hacer esta operación mientras yo estoy aquí. 128 00:12:45,000 --> 00:12:54,000 Después, como ya he utilizado y no lo tengo que utilizar más, lanzo una sem.release 129 00:12:54,000 --> 00:13:00,000 que sustancialmente desbloquea la posibilidad de acceder aquí a los otros threads, 130 00:13:00,000 --> 00:13:03,000 al otro thread en este caso que estaba conectado. 131 00:13:03,000 --> 00:13:09,000 Entonces, independientemente de quién sea el que accede, si él tenga el thread 1 o el thread 2, 132 00:13:09,000 --> 00:13:19,000 en cada momento sólo podrá ejecutar esta operación quien ha pasado este bloqueo, digamos, este sem.acquire, 133 00:13:19,000 --> 00:13:28,000 quien habrá encontrado el semáforo con valor 1, habrá cambiado el semáforo a 0 y habrá podido acceder aquí, 134 00:13:28,000 --> 00:13:36,000 mientras el otro que habrá encontrado el semáforo ya a 0 tendrá que esperar hasta que se haga una release, 135 00:13:36,000 --> 00:13:39,000 se vuelva a estar a 1 y entonces puede entrar. 136 00:13:39,000 --> 00:13:46,000 Entonces, de esta forma, esta operación se hace en mutua exclusión, o sea que o uno o el otro. 137 00:13:46,000 --> 00:13:56,000 Entonces, si ahora ejecuto este ejercicio, veo que el resultado ahora es el 20.000 que me esperaba desde el principio. 138 00:13:57,000 --> 00:13:58,000 Así. 139 00:14:00,000 --> 00:14:05,000 Vale, vamos a ver otros ejemplos de uso de semáforo. 140 00:14:05,000 --> 00:14:15,000 Por ejemplo, podemos crear una pequeña cola de login, digamos así, ¿vale? 141 00:14:15,000 --> 00:14:22,000 O sea que lo que queremos hacer con esto es que limitar el acceso a un determinado sistema. 142 00:14:22,000 --> 00:14:31,000 Imaginemos que nosotros tenemos un sistema y que queremos que en un determinado momento solo n threads puedan acceder 143 00:14:31,000 --> 00:14:34,000 y puedan trabajar con este sistema, es decir, n usuarios, ¿vale? 144 00:14:34,000 --> 00:14:44,000 Con cada usuario asociado a un thread, pues entonces nos creamos esta pequeña clase que es la que gestiona el login, ¿vale? 145 00:14:44,000 --> 00:14:58,000 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? 146 00:14:58,000 --> 00:15:02,000 Y esto será el valor que pongo en el semáforo. 147 00:15:02,000 --> 00:15:10,000 Luego nos creamos esta TryLogin, que sustancialmente es una operación para intentar logearse. 148 00:15:10,000 --> 00:15:18,000 Si ésta devuelve un true, entonces he podido logearme porque había todavía espacios, y en vez ésta me devuelve false, 149 00:15:18,000 --> 00:15:22,000 quiere decir que he intentado logearme pero no lo he conseguido. 150 00:15:22,000 --> 00:15:26,000 Fijaos que lo que hace es simplemente esto. 151 00:15:26,000 --> 00:15:33,000 Esta operación de aquí es un poquito distinta de la que hemos visto antes, que era la Acquire. 152 00:15:33,000 --> 00:15:44,000 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, 153 00:15:44,000 --> 00:15:50,000 o se bloquea aquí, ¿vale? Y se queda bloqueado hasta que alguien haga la release. 154 00:15:50,000 --> 00:15:59,000 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, 155 00:15:59,000 --> 00:16:02,000 o si no, se queda ahí. 156 00:16:02,000 --> 00:16:07,000 Sin embargo la Try Acquire no es bloqueante, lo intenta. 157 00:16:07,000 --> 00:16:17,000 Si devuelve true, quiere decir que he podido acceder al recurso y ahora es mío, entonces lo puedo utilizar. 158 00:16:17,000 --> 00:16:26,000 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. 159 00:16:26,000 --> 00:16:35,000 Está la Logout, que sustancialmente hace una release sobre el semáforo, entonces aumenta el número que le he pasado. 160 00:16:35,000 --> 00:16:46,000 Y luego está la Avalo Slot, que me dice cuántos, sustancialmente cuántos es el valor actual del semáforo. 161 00:16:46,000 --> 00:17:00,000 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. 162 00:17:01,000 --> 00:17:17,000 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. 163 00:17:17,000 --> 00:17:32,000 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. 164 00:17:33,000 --> 00:17:59,000 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. 165 00:17:59,000 --> 00:18:16,000 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. 166 00:18:17,000 --> 00:18:43,000 ¿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. 167 00:18:43,000 --> 00:19:05,000 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. 168 00:19:05,000 --> 00:19:34,000 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. 169 00:19:36,000 --> 00:19:45,000 Cuando he acabado, entonces ya no necesito el recurso, ya no necesito trabajar aquí, lo que hago es un Logout y acabo. 170 00:19:46,000 --> 00:20:08,000 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. 171 00:20:09,000 --> 00:20:10,000 Vamos a ejecutarlo. 172 00:20:10,000 --> 00:20:39,000 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, 173 00:20:39,000 --> 00:20:57,000 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. 174 00:20:58,000 --> 00:21:04,000 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. 175 00:21:05,000 --> 00:21:24,000 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. 176 00:21:25,000 --> 00:21:40,000 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. 177 00:21:40,000 --> 00:22:01,000 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. 178 00:22:02,000 --> 00:22:14,000 ¿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. 179 00:22:14,000 --> 00:22:32,000 ¿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. 180 00:22:32,000 --> 00:22:45,000 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. 181 00:22:45,000 --> 00:23:03,000 ¿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? 182 00:23:03,000 --> 00:23:18,000 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? 183 00:23:18,000 --> 00:23:34,000 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 184 00:23:34,000 --> 00:23:51,000 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, 185 00:23:51,000 --> 00:24:06,000 é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? 186 00:24:06,000 --> 00:24:20,000 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. 187 00:24:20,000 --> 00:24:38,000 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? 188 00:24:38,000 --> 00:24:56,000 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? 189 00:24:56,000 --> 00:25:22,000 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? 190 00:25:23,000 --> 00:25:35,000 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? 191 00:25:35,000 --> 00:25:47,000 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. 192 00:25:48,000 --> 00:26:09,000 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? 193 00:26:09,000 --> 00:26:28,000 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? 194 00:26:29,000 --> 00:26:55,000 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? 195 00:26:56,000 --> 00:27:06,000 Ahora, cuando me interrumpe, si me interrumpe, entonces accedo a esta parte de aquí y luego vuelvo otra vez a dormir, ¿vale? 196 00:27:07,000 --> 00:27:18,000 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? 197 00:27:19,000 --> 00:27:40,000 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? 198 00:27:41,000 --> 00:27:56,000 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? 199 00:27:57,000 --> 00:28:11,000 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? 200 00:28:11,000 --> 00:28:30,000 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, 201 00:28:30,000 --> 00:28:49,000 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? 202 00:28:49,000 --> 00:29:04,000 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 203 00:29:04,000 --> 00:29:21,000 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, 204 00:29:21,000 --> 00:29:36,000 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, 205 00:29:36,000 --> 00:29:50,000 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. 206 00:29:50,000 --> 00:30:07,000 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, 207 00:30:07,000 --> 00:30:26,000 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, 208 00:30:26,000 --> 00:30:41,000 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, 209 00:30:41,000 --> 00:30:56,000 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. 210 00:30:56,000 --> 00:31:18,000 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, 211 00:31:18,000 --> 00:31:31,000 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. 212 00:31:31,000 --> 00:31:48,000 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, 213 00:31:48,000 --> 00:32:11,000 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 214 00:32:11,000 --> 00:32:29,000 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, 215 00:32:29,000 --> 00:32:47,000 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? 216 00:32:47,000 --> 00:33:15,000 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. 217 00:33:16,000 --> 00:33:37,000 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. 218 00:33:37,000 --> 00:33:56,000 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. 219 00:33:56,000 --> 00:34:10,000 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í. 220 00:34:11,000 --> 00:34:31,000 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. 221 00:34:32,000 --> 00:34:52,000 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. 222 00:34:53,000 --> 00:35:10,000 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, 223 00:35:10,000 --> 00:35:24,000 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, 224 00:35:25,000 --> 00:35:34,000 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, 225 00:35:34,000 --> 00:35:49,000 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. 226 00:35:50,000 --> 00:36:10,000 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, 227 00:36:11,000 --> 00:36:25,000 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, 228 00:36:26,000 --> 00:36:39,000 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, 229 00:36:40,000 --> 00:36:57,000 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, 230 00:36:58,000 --> 00:37:07,000 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, 231 00:37:07,000 --> 00:37:21,000 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á, 232 00:37:22,000 --> 00:37:37,000 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í, 233 00:37:37,000 --> 00:37:48,000 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, 234 00:37:48,000 --> 00:37:58,000 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, 235 00:37:58,000 --> 00:38:15,000 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, 236 00:38:15,000 --> 00:38:27,000 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, 237 00:38:27,000 --> 00:38:41,000 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í, 238 00:38:41,000 --> 00:38:55,000 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, 239 00:38:55,000 --> 00:39:11,000 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í, 240 00:39:11,000 --> 00:39:32,000 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, 241 00:39:33,000 --> 00:39:53,000 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, 242 00:39:54,000 --> 00:40:10,000 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, 243 00:40:10,000 --> 00:40:34,000 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, 244 00:40:35,000 --> 00:41:01,000 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, 245 00:41:02,000 --> 00:41:21,000 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, 246 00:41:21,000 --> 00:41:37,000 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, 247 00:41:37,000 --> 00:42:02,000 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, 248 00:42:03,000 --> 00:42:20,000 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 249 00:42:20,000 --> 00:42:36,000 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 250 00:42:36,000 --> 00:42:54,000 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, 251 00:42:54,000 --> 00:43:08,000 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, 252 00:43:08,000 --> 00:43:21,000 fijaos que no todos los semáforos pueden tener valor en la misma parte, en la misma sección crítica. Y ya está. 253 00:43:22,000 --> 00:43:39,000 Ú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, 254 00:43:39,000 --> 00:44:00,000 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, 255 00:44:01,000 --> 00:44:15,000 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. 256 00:44:15,000 --> 00:44:33,000 ¿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, 257 00:44:33,000 --> 00:44:55,000 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, 258 00:44:55,000 --> 00:45:17,000 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á.