image.png

  1. Se utiliza el modelo de actores donde cada actor cuenta con un semáforo en su estado interno y mediante los mensajes Acquire y Release ejecuta dichas funciones sobre semáforo en cuestión. Esto no es de utilidad dado que no se dará ningún tipo de sincronización con ese semáforo por el simple hecho de que solo su actor puede acceder a él. Lo lógico sería que exista un único semáforo compartido entre distintos hilos para aprovechar realmente la funcionalidad de este, y si por ejemplo se busca sincronizar el acceso a un recurso exista también un Lock al que todos estos hilos tengan una referencia.
  2. En este caso se observa un busy wait dado que en caso de no estar disponible el recurso se vuelve a enviar a sí mismo el mismo mensaje hasta que el recurso en cuestión se encuentre listo. Si se busca evitar esto se podría hacer uso de semáforos para sincronizar el acceso a estos recursos.

image.png

  1. Falso → Son útiles para procesos I/O intensivos como los que esperan por entrada del usuario, lectura de archivos o también solicitudes a servicios externos. En este caso los hilos tradicionales presentan mayor utilidad.
  2. Falso → El estado interno de un actor no requiere de locks para ser accedido dado que solo el mismo actor puede acceder a su estado, no se trata de un estado mutable compartido y por ende no existen en él las zonas de exclusión mutua.
  3. Falso → Las sub-tareas que se resuelven mediante el modelo fork-join son independientes unas de otras y cada hilo lleva a cabo una única sub-tarea a la vez, por lo tanto hay posibilidad de que existan race conditions en este modelo.
  4. Falso → No es necesario un hilo debe hacer uso de aquello que comparte su estado entre hilos al momento de requerir su uso. La zona de exclusión mutua debe ser lo más pequeña posible de modo que el hilo ejecute dicha región de código lo más rápido posible.

image.png

  1. Programación asíncrona → Si precisamos realizar distintas consultas en simultáneo para posteriormente mostrar un resultado podemos hacer uso de tareas que se resuelvan de forma concurrente.
  2. Sección crítica / Locks → Al tratarse de un multijugador masivo existirán muchos recursos a los que los jugadores tendrán la posibilidad de acceder. Supongamos que en una aldea hay un NPC que cada día otorga una misión a los primeros N jugadores que se acercan a hablarle, cuando un jugador M (M ≤ N) se acerca a interactuar obtiene una nueva misión y el contador de misiones del NPC disminuye en 1, pero este acceso al estado interno del NPC debe ser exclusivo y controlado. Por lo tanto un jugador no puede interactuar con el NPC mientras este interactúa con otro jugador, y si el jugador es el N + 1 no podrá obtener una misión.
  3. Programación asíncrona → El crawler accederá a los contenidos de forma concurrente siempre y cuando estos no dependan de otros que deban ser previamente obtenidos, en esos casos el acceso será secuencial. Principalmente buscamos que mediante tareas ejecutadas de forma concurrente sea posible incrementar la performance de modo que si X cantidad de recursos no presentan una interdependencia, las tareas de descarga de cada uno de ellos de ejecuten de forma concurrente.
  4. Fork Join → El conjunto de videos a analizar representa un conjunto de tareas, cada video representa una sub-tarea y por lo tanto la idea es lanzar hilos que resuelvan esas subtareas, además la existencia de sub-tareas cuya resolución resulte más compleja (videos más largos) nos dará la oportunidad de hacer uso de work stealing para que aquellos hilos que ya hayan finalizado todas sus sub-tareas puedan seguir realizando otras y resultando en que la aplicación sea más performante. Finalmente el join se trata de generar ese nuevo video a partir del análisis realizado sobre las sub-tareas previamente resueltas.

image.png

Identifico a un actor por cada invitado y otro actor coordinador para el pool compartido. La idea es que los invitados envíen mensajes de solicitud de consumo al pool al momento de querer pagar un juego, si hay saldo disponible en el mismo para que el invitado juegue entonces la respuesta del pool será afirmativa (true) y en caso contrario negativa (false). Si el saldo es mayor o igual al monto de la solicitud puede jugar y el saldo disminuye en función de tal monto pero en caso contrario le comunica que no puede jugar y el saldo permanece igual. Al comenzar la ejecución un invitado se envía a sí mismo el mensaje ElegirJuego, de esta forma inicia el ciclo donde tras elegir un juego (supongamos que esta función hace un sleep y devuelve un monto) se envía una solicitud al pool para ver si ese invitado puede o no jugar, en ambos casos termina nuevamente enviándose el mismo mensaje ElegirJuego para continuar el ciclo.

Se pueden pensar las siguiente mejoras (que entiendo exceden al objetivo del ejercicio):

const INVITADOS: usize = N;

/* Tipos de actores */

struct Invitado {
	id: usize;
	
	fn started(&mut self, _ctx: &mut Context<Self>) {
     println!("[INVITADO {}] ¡ A jugar !", self.id);
     _ctx.address().try_send(ElegirJuego());
  }
}

impl Actor for Invitado {
	type Context = Context<Self>;
}

struct Pool {
	saldo: f64
	pool: Recipient<Result>
}

impl Actor for Pool {
	type Context = Context<Self>
}

/* Tipos de mensajes */

struct SolicitudConsumo {
	id_invitado: usize,
	monto: f64,
	invitado: Recipient<Result>
}

struct RespuestaConsumo {
	resultado: bool
}

struct ElegirJuego {
	
}

/* Manejo de mensajes */

impl Handler<SolicitudConsumo> for Pool {
    type Result = ();

    fn handle(&mut self, msg: SolicitudConsumo, _ctx: &mut Context<Self>) -> Self::Result {
				let id_invitado = msg.0;
        let monto_solicitado = msg.1;
        let invitado = msg.2;

        println!("[POOL] Recibo una solicitud de {} por un monto de {}.", id_invitado, monto_solicitado);
        
        if self.saldo >= monto_solicitado {
	        self.saldo -= monto_solicitado;
	        println!("[POOL] Hay saldo suficiente, saldo restante {}.", self.saldo);
					invitado.try_send(RespuestaConsumo{ resultado: true }).unwrap();
        } else {
	        println!("[POOL] Sin saldo suficiente, saldo restante {}.", self.saldo);
					invitado.try_send(RespuestaConsumo{ resultado: false }).unwrap();	        
        }

    }
}

impl Handler<RespuestaConsumo> for Invitado {
		type Result = ();
		
		fn handle(&mut self, msg: RespuestaConsumo, _ctx: &mut Context<Self>) -> Self::Result {
				let puedo_jugar = msg.0;
				
				if (!puedo_jugar) {
					println!("[INVITADO {}] No puedo jugar, elijo otro juego.", self.id_invitado);
				} else {
					println!("[INVITADO {}] A jugar !!!", self.id_invitado);
					simular_juego();
				}
				_ctx.address().try_send(ElegirJuego());
		}
}

impl Handler<ElegirJuego> for Invitado {
		type Result = ();
		
		fn handle(&mut self, msg: ElegirJuego, _ctx: &mut Context<Self>) -> Self::Result {
				println!("[INVITADO {}] ¿Ahora a qué juego?", self.id_invitado);
				let nuevo_monto =	simular_eleccion_juego();
				println!("[INVITADO {}] Sé a qué quiero jugar ahora !!!", self.id_invitado);
				self.pool.try_send(
						SolicitudConsumo {
									id_invitado: self.id,
									monto: nuevo_monto,
									invitado: _ctx.address().recipient()
							}
				)
}

fn main() {
	let system = System::new();
    
  system.block_on(async {
      let mut invitados = vec!();

      for id in 0..INVITADOS {
          invitados.push(Invitado { id }.start().recipient())
      }

      Pool { 
          saldo: 1000000.0,
          invitados
      }.start();
  });

  system.run().unwrap();
  println!("No hay más saldo disponible.");
}

image.png