Anuncios

Bienvenidos sean a este post, hoy veremos la teoria sobre un tema derivado de multithreading.

Anuncios
Anuncios

Programar puede ser bastante complejo, y mas si comenzamos a agregar concurrencia, siendo mucho mas facil trabajar con codigo sincronico ejecutado secuencialmente. Esto conlleva a que muchos sistemas eviten utilizar multithreading y en su lugar utilizar conceptos de programacion manejados por eventos. Un ejemplo de este es un bucle de eventos, event loop, el cual nos perrmitira tener un enfoque administrable para la programacion asincronica. Esto permitir relacionar a los eventos que podemos tener en una GUI; como son la presion de un boton, soltar un boton o movimiento del mouse sobre ellos. Como esto representa un pedazo de informacion, nos permite que el programa reaccione apropiadamente ante alguno de ellos. Un enfoque muy popular es aplicar el event loop para que enliste cualquier evento ocurrido durante la interaccion del usuario. La idea es que los eventos enlistados sean procesados en algun momento en el futuro, en el mismo orden que fueron ingresados.

Anuncios

La introduccion de multithreading nos agrega complejidad. Esto hara que debamos tener cuidado con la condicion de carrera, el manejo apropiado de los threads, inclusive la aplicacion de un pool de threads, tal como vimos en posts anteriores. En cambio, con codigo ejecutado secuencialmente solo te preocupas por el codigo y solo por el codigo pero en multithreading tambien debemos observar su forma de ejecucion.

Anuncios

Para entender esto que comentamos, tomemos como ejemplo un patron de diseño simple como es singleton para ver como se comporta en un entorno multithreading. Veamos la clasica implementacion de un singleton:

class Singleton
{
public:
  static Singleton* get_instancia() {
    if (instancia_ == nullptr) {
      instancia_ = new Singleton();
    }
    return instancia_;
  }

  // codigo omitido por brevedad
private:
  static inline Singleton* instancia_ = nullptr;
};
Anuncios

Los singleton estan creados para generar instancias unicas, por esta razon su constructor se establece en la parte privada y no puede ser accedida desde la instancia. Y en la parte publica tendran un metodo que verifica si la instancia es nula. En caso de ser verdadero lo crea y lo asigna, de lo contrario siempre devolvera esa instancia. El resto sera como una clase comun con mas metodos y propiedades. Con esto comentado, veamos como aplicarlo:

void crear_algo_unico() 
{
  Singleton* inst = Singleton::get_instancia();
  // resto de las instrucciones...
}

void crear_algo_util() 
{
  Singleton* otraInst = Singleton::get_instancia();
  // resto de las instrucciones...
}  

std::thread t1{crear_algo_unico};
std::thread t2{crear_algo_util};
t1.join();
t2.join();
Anuncios

Aqui tenemos dos funciones que crearan dos instancias del Singleton anterior. Y luego creamos dos threads donde pasaremos cada funcion anterrior. En este codigo cabe la posibilidad de que se generen las dos instancias al mismo tiempo y se genere una condicion de carrera. Modifiquemos al singleton de la siguiente manera:

class Singleton
{
public:
  static Singleton* get_instancia() {
    if (instancia_ == nullptr) {
	std::lock_guard lg{mutex_};
	if (instancia_ == nullptr) {
		instancia_ = new Singleton();
	}
    }
    return instancia_;
  }

  // codigo omitido para brevedad
private:
  static std::mutex mutex_;
  static inline Singleton* instancia_ = nullptr;
};
Anuncios
Anuncios

Primero crearemos un mutex en la parte privada para poder bloquearlo. En la parte privada agregamos un condicional donde volvemos a chequear si la instancia es nula. Si se cumple la condicion bloqueamos al mutex y volvemos a verificar lo mismo donde si se cumple generamos la instancia. Pero por que lo chequeamos dos veces? La respuesta es sencilla, al momento del primer chequeo puede repetirse la circunstancia de que haya mas de un thread con una instancia vacia y genere la condicion de carrrera nuevamente. Pero ahora tenemos al guardian de bloqueo, esto hace que un solo thread tenga acceso, el resto esperara a que se desbloquee el mutex. Si ya existe la omite de lo contrario la crea.

Anuncios
Anuncios

Obviamente esto degradara mucho la performance del codigo comparado con uno sincronico pero nos sirve para ir entendiendo como es un codigo concurrente. Como podran ver el diseño de codigo concurrente esta muy basado en las capacidades del lenguaje mismo. La evolucion de C++ colaboro mucho que pueda usarse esto, porque en las primeras versiones casi no existia el soporte de multithreading y con todo lo comentado en los posts anteriores nos proveen unas poderosas herramientas tales como las corutinas pero eso sera un tema para un proximo post.

Anuncios

En resumen, hoy hemos visto como diseñar un codigo concurrente, hemos tomado como ejemplo a patron como es singleton, hemos visto como adaptarlo para ser usado en multithreading. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.

Anuncios

Donation

It’s for maintenance of the site, thanks!

$1.50