Publi

Singletons en C++ y alguna nota sobre thread safety (I)

Antes de nada, comentar que he dividido este post en dos porque vi que se estaba alargando demasiado y se lanzarán uno al día, pondré aquí enlaces a todos los posts.

Muchas veces cuando estamos programando tenemos la necesidad de crear un objeto de una clase determinada, pero éste objeto deberá ser creado una sola vez en nuestra aplicación y debemos evitar a toda costa que pueda ser creado más veces. Podemos pensar en:

  • Una conexión de base de datos para nuestra aplicación. Bueno, a veces necesitamos varias, pero muchas veces sólo necesitamos una, y queremos referirnos a ella en cualquier punto de nuestro programa (y no queremos una variable global ni nada de eso).
  • Un objeto de configuración general del programa. Imaginemos que en el constructor leemos el archivo de configuración y generamos un árbol con las opciones de nuestra aplicación. Dicho árbol no debe ser generado más veces, sólo cuando arrancamos el programa.
  • Un logger, que configuraremos una sola vez y nos referiremos al objeto cuando queramos escribir algo.
  • Almacén de caché. Por ejemplo para almacenar valores traídos de base de datos que vamos a utilizar en el transcurso de nuestro programa, así evitamos traerlos más veces.
  • Acceso a un recurso exclusivo, por poner un ejemplo un hardware especializado.
  • Muchos más usos.

También es cierto que para muchos casos el uso del patrón Singleton no es obligatorio, en otros casos no tenemos que usarlo y mucha gente desaconseja su uso, pero es una de las muchas herramientas que tenemos en este mundo 🙂

Primera aproximación

El caso más sencillo que podremos hacer es el siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Singleton
{
public:
  static Singleton *getInstance()
  {
    if (instance == NULL)
      instance = new Singleton();
    else
      std::cout << "Getting existing instance"<<std::endl;

    return instance;
  }

protected:
  Singleton()
  {
    std::cout << "Creating singleton" << std::endl;
  }

  virtual ~Singleton()
  {
  }

  Singleton(Singleton const&); 
  Singleton& operator=(Singleton const&);

private:
  static Singleton *instance;
};

Singleton* Singleton::instance=NULL;

De esta forma nos aseguramos de que el objeto sólo lo creamos una vez. Y por lo tanto, si durante la ejecución de nuestro programa queremos utilizar nuestro objeto de clase Singleton, tenemos que hacer:

1
Singleton *s = Singleton::getInstance();

En este caso, si la instancia no está creada, la creará (hay que observar que el constructor de la clase está protegido por lo tanto sólo se puede utilizar dentro de la clase Singleton y en clases derivadas), y si está creado utilizará la clase instanciada previamente (almacenada en el atributo instance). La magia está en que, tanto el atributo como el método getInstance() son estáticos, éstos dos serán globales para la clase y nos devolverán el objeto instanciado (que sólo podrá ser uno), y serán los métodos que creemos para nuestra clase los que se basen en cada objeto instanciado.

Por otro lado, el constructor de copia y el operador de asignación están protegidos para evitar que se puedan utilizar en nuestro programa y alguien pueda chafarnos este precioso funcionamiento, por ejemplo pudiendo crear dos instancias de esta clase (nuestra perdición).

En cuanto al destructor, nosotros elegimos, ¿queremos que se pueda destruir la clase? Podemos poner el destructor en la parte pública, así con un simple:

1
destroy s;

se podrá destruir la instancia de nuestro singleton (debemos tener cuidado y hacer que el método instance valga NULL de nuevo, o la próxima vez que utilicemos la instancia obtenida por getInstance() se puede liar).
Otra opción sería crear otro método estático:

1
2
3
4
5
6
7
8
void destroyInstance()
{
  if (instance != NULL)
  {
    destroy instance;
    instance = NULL;
  }
}

y mantener el destructor de nuestra clase protegido (o hacerlo privado). En estos método podemos poner más código, dependiendo del uso de nuestra clase. También podemos optar por la función atexit() de que llama a una serie de funciones (establecidas por nosotros) cuando el programa finaliza (siempre que termine bien, con return, o exit()).

¿Qué pasa cuando mi programa es multi-hilo?

Lo que hemos visto hasta ahora funciona bien cuando el programa tiene un sólo thread (o hilo), es decir, sólo tenemos una línea de órdenes en ejecución. Por un lado podemos pensar en procesadores de varios núcleos, es cierto que son capaces de ejecutar varias órdenes al mismo tiempo, pero nuestro programa puede aprovechar sólo un núcleo, por lo que normalmente los demás núcleos que no aprovechamos con una aplicación, el sistema operativo los destina a otros procesos, y no pasa nada.
El problema viene cuando, para aprovechar la tecnología de que disponemos, queremos aprovechar al máximo el procesador dentro de nuestra aplicación (imaginad un procesamiento matemático complejo) y queremos que nuestra aplicación pueda realizar varias cosas a la vez, es decir nuestra aplicación pueda estar ejecutando varias tareas simultáneamente. Aunque este concepto se asocia casi siempre con varios núcleos no siempre es así y a veces podemos ganar velocidad en un sistema mono-núcleo utilizando varios hilos (y también tendríamos el problema con los singletons) aprovechando esperas generadas, por ejemplo, por interacción con dispositivos.

Bueno, ¿ qué puede ocurrir ? Pues que varios hilos quieran simultáneamente solicitar una instancia de nuestro Singleton, y si es la primera vez, va a intentar crear una instancia, pero qué pasaría si un proceso entra en getInstance() y pasa el «if (instance == NULL)», entra en el constructor, y al mismo tiempo otro proceso entra también en getInstance() ? Pues que para cada proceso se creará una nueva instancia ya que no ha dado tiempo a asignar el valor del atributo instance, y eso puede pasar con varios procesos.

El primer problema es el multi-thread, C++ (versiones anteriores a C++11) no trae de forma nativa soporte para threads, por lo tanto, para los ejemplos utilizaré la biblioteca pthread que se podrá compilar en cualquier *nix, y también el ejemplo de C++11 para compiladores modernos, vamos a lanzar varios getInstance() en threads concurrentes, a ver qué pasa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <pthread.h>
#include <cstdlib>
#include <unistd.h>

using namespace std;

class Singleton
{
public:
  static Singleton *getInstance()
  {
    if (instance == NULL)
      instance = new Singleton();
    else
      std::cout << "Getting existing instance"<<std::endl;

    return instance;
  }

protected:
  Singleton()
  {
    std::cout << "Creating singleton" << std::endl;
  }

  virtual ~Singleton()
  {
  }

  Singleton(Singleton const&); 
  Singleton& operator=(Singleton const&);

private:
  static Singleton *instance;
};

Singleton* Singleton::instance=NULL;

void *task (void*)
{
  Singleton *s = Singleton::getInstance();
  cout << "Thread con instancia"<<endl;
}

int main()
{
  for (unsigned i=0; i<100; ++i)
    {
      pthread_t thread;
      int rc = pthread_create(&thread, NULL, task, NULL);
    }

  pthread_exit(NULL);
  return 0;
}

Para compilar este ejemplo, hacemos gcc -o singlethread singlethread.cpp -lpthread

En ejemplo en C++11 es el siguiente (sí, podemos hacerlo mucho más sencillo aquí):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <thread>
#include <cstdlib>
#include <unistd.h>

using namespace std;

class Singleton
{
public:
  static Singleton *getInstance()
  {
    if (instance == nullptr)
      instance = new Singleton();
    else
      std::cout << "Getting existing instance"<<std::endl;

    return instance;
  }

protected:
  Singleton()
  {
    std::cout << "Creating singleton" << std::endl;
  }

  virtual ~Singleton()
  {
  }

  Singleton(Singleton const&); 
  Singleton& operator=(Singleton const&);

private:
  static Singleton *instance;
};

Singleton* Singleton::instance=nullptr;

int main()
{
  for (unsigned i=0; i<100; ++i)
    {
      thread([](){
      Singleton *s = Singleton::getInstance();
      cout << "Thread con instancia"<<endl;
    }).detach();
    }

  return 0;
}

Para compilar este con g++ : g++ -o singlethread11 singlethread11.cpp -std=c++11 -lpthread (C++ como lenguaje es más rápido e intuitivo, pero para este compilador, utilizamos la biblioteca de threads POSIX como antes).

En ambos casos en la salida podremos ver varios mensajes «Creating singleton» y hemos demostrado nuestro estrepitoso fallo en este sentido, nuestro Singleton se ha creado varias veces, y eso puede resultar desastroso para nuestro programa. En el caso de utilizar una estructura de este tipo para gestionar la configuración de nuestra aplicación, estaremos manteniendo varias veces en memoria la configuración, en principio no es demasiado malo, pero si modificamos la configuración para posteriormente guardarla, sólo se modificará una de las estructuras y al guardarse tendremos un problema.

Mañana, nos aproximamos al thread safety un poco más.

Modificado 22/04/2014: Gracias a Luis Cabellos por su sugerencia, he cambiado en el ejemplo de C++11 los NULL por nullptr.
Foto: Nan Palmero (Flickr CC-by)

También podría interesarte....

There are 25 comments left Ir a comentario

  1. Sebastián /
    Usando Mozilla Firefox Mozilla Firefox 28.0 en Windows Windows XP

    Que tal, sólo quería informarte que no puedo visualizar los bloques de código. Sólo el primer bloque, en el cual se define la clase Singleton, se visualiza, en los demás indica lo siguiente:
    «GeSHi Error: GeSHi could not find the language ccp (using path /home/gaspy/www/totaki.com/www/poesiabinaria/wp-content/plugins/codecolorer/lib/geshi/) (code 2)». Sólo lo he probado en Firefox y chrome. Espero se pueda solucionar, dado que me interesa mucho el artículos.
    Saludos y gracias.

  2. Gaspar Fernández / Post Author
    Usando Mozilla Firefox Mozilla Firefox 28.0 en Ubuntu Linux Ubuntu Linux

    @Sebastián
    Sebastian !! Muchas gracias por tu comentario, se me pasó, al plugin que uso para los bloques de código le dije ccp en lugar de cpp y se lió todo. Aquí tienes el código, y mañana se publica también la segunda parte del artículo.

  3. Pingback: BlogESfera.com /

  4. Luis Cabellos /
    Usando Mozilla Firefox Mozilla Firefox 28.0 en Ubuntu Linux Ubuntu Linux

    NULL en C++11 se escribe nullptr y el uso de auto mejora aun más el código, e.g:

    auto s = Singleton::getInstance();

    Por lo demas, espero la siguiente parte del articulo 🙂

    1. Gaspar Fernández / Post Author
      Usando Mozilla Firefox Mozilla Firefox 28.0 en Ubuntu Linux Ubuntu Linux

      Gracias Luis. Cambio el NULL en el ejemplo. nullptr está muy bien y ¡no me acostumbro a usarlo! no puede ser.
      Por lo del auto, yo lo veo más por pereza y me encanta, mi uso preferido está en los iteradores, cuando tenemos un map >::iterator lo cambias por un auto, y te da hasta alegría 🙂 pero es sólo un Singleton* (más del doble de letras que auto, pero bueno)

  5. Pingback: Singleton en C++ thread safe y recomendaciones /

  6. pikachu /
    Usando Google Chrome Google Chrome 81.0.4044.138 en Windows Windows NT

    You’ve written nice post, I am gonna bookmark this page, thanks for info. I actually appreciate your own position and I will be sure to come back here. Thanks. wheelie cross

  7. carlhamsmmond /
    Usando Google Chrome Google Chrome 104.0.0.0 en Windows Windows NT

    The controls for the game are also very easy to use: hold the trigger to speed up, use the analog joystick to move the car, and hold the secondary trigger to use the emergency brake when you want to drift. In addition to these controls, you can also add a clutch, which makes it even more difficult to add manual speed controls. Read on to find out why we think this is one of the best drifting games for PS4, then check the price to get your own copy. octordle

  8. Mike Rooney /
    Usando Google Chrome Google Chrome 108.0.0.0 en Windows Windows NT

    Your blog provided us with valuable information. I am looking forward to read more blog posts from here keep it up!!
    Yellowstone Official Merchandise

  9. jessica andrea /
    Usando Google Chrome Google Chrome 109.0.0.0 en Windows Windows NT

    Singleton pattern is not required but in many cases you should use it, especially for death run 3d, it will bring more efficiency

  10. sara jane /
    Usando Google Chrome Google Chrome 109.0.0.0 en Windows Windows NT

    Thank you very much, I have been seeking for this information for a very long time, and the information that you provided in your article has been very helpful to me in my work. gacha life

  11. karanpc /
    Usando Google Chrome Google Chrome 109.0.0.0 en Windows Windows NT

    We exlusively unlocked more than 400 engineering software: CYPE, karanpc GEO5, Mensura Genius, COVADIS, Allplan, Graitec.

  12. adamschule85 /
    Usando Google Chrome Google Chrome 112.0.0.0 en Windows Windows NT

    Your blog is a great one. What really impresses me is that you are correctly mentioned that there are thousands of tools that are available to create a website or launch one but what matters is that you choose the right one, the one that gives you all that is actually needed. House Cleaning Lethbridge

  13. ones /
    Usando Google Chrome Google Chrome 112.0.0.0 en Windows Windows NT

    A strategic game, Tower Defense puts you up against many waves of enemies as you place and upgrade towers in key strategic areas to defend your base.

  14. OKBet /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    Your work is very good and I appreciate you and hopping for some more informative posts. Thank you for sharing great information to us.
    Athletes: Why Change Nationalities?

  15. roblox character boy /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    I enjoyed surfing around your blog review. Thanks for sharing this wonderful information, If you want to know what is Roblox Character Girl? Users of the online gaming platform «Roblox» can design their own video games and virtual worlds. Users can design and construct their characters, or avatars, within the platform to represent themselves in the virtual world.

  16. Nord /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    The Nordvpn mod apk offers users numerous benefits, including unrestricted access to geo-restricted content, allowing seamless streaming and downloading from anywhere in the world. With its enhanced privacy and security features, such as anonymous browsing and protection against cyber threats, this modified version guarantees a safer and more enjoyable online experience. Moreover, the NordVPN mod APK provides faster connection speeds, optimizing internet usage for smooth browsing and streaming activities.

  17. Mike Rooney /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    Your blog provided us with valuable information. I am looking forward to read more blog posts from here keep it up!!Top Gun Bomber Jacket

  18. Andrew Mark /
    Usando Google Chrome Google Chrome 114.0.0.0 en Windows Windows NT

    This is excellent article, thank you for the share! This is what I am looking for, hope in future you will continue sharing such an superb work.
    Red Cobra Kai Jacket

  19. MB WhatsApp /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    Your blog has given us useful knowledge. I want to read more blog entries from you in the future. Yo WhatsApp

  20. UFAAUTO789 /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    ufabet auto เว็บพนันบอลที่ดีที่สุด เดิมพันง่าย 24ชม. แนะนำเกมสล็อต เกมน่าเล่น ทำกำไรง่าย แจ็คพอตแตกกระจาย

  21. Rajiv /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    Discovering DPWalay has been an absolute game-changer for me! 📸✨ Their collection of DP images is like a treasure trove of creativity and expression. Whether I’m in the mood for something fun, elegant, or motivational, DPWalay always has the perfect image to match. It’s my one-stop-shop for giving my profiles a fresh and exciting look. Kudos to the team behind DPWalay for curating such a diverse and captivating range of images

  22. James /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    Thanks for the great article guys!

    Website design agency in Gladstone

  23. John /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    Thanks for the great post guys.

    Wollongong floor sanding company

  24. Jimmy /
    Usando Google Chrome Google Chrome 115.0.0.0 en Windows Windows NT

    Thank you guys, look forward to more.

    Tim – Check us out

Leave a Reply to pikachu Cancle Reply