2012-09-03 20 views
13

Esta pregunta se realizó en una entrevista. La primera parte fue escribir la clase Singleton:Singleton con multiprocesadores

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 

    public: 
    static Singleton* getSingletonInstance() 
    { 
     if(singletonInstance == null) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 
}; 

Entonces se le preguntó cómo manejar esta getSingletonInstance() en una situación multiproceso. Yo no estaba muy seguro, pero he modificado como:

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 
    static mutex m_; 

    public: 
    static Singleton* getSingletonInstance() 
    { 
     m_pend(); 
     if(singletonInstance == null) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 

    static void releaseSingleton() 
    { 
     m_post(); 
    } 
}; 

Después me dijeron que, si bien se requiere un mutex, a la espera y la publicación de un mutex no es eficiente ya que toma tiempo. Y hay una mejor manera de manejar esta situación.

¿Alguien sabe una manera mejor y más eficiente de manejar la clase Singleton en una situación multiproceso?

+12

¿No usa singleton en absoluto? –

+6

Usar el estado global en un código multiproceso es una buena forma de obtener infinitos dolores de cabeza. Doblemente así en caso de stupidgleton. –

+0

@CatPlusPlus: es simplón. Simplón. –

Respuesta

20

En C++ 11, se garantiza la siguiente para realizar thread-safe inicialización:

static Singleton* getSingletonInstance() 
{ 
    static Singleton instance; 
    return &instance; 
} 

En C++ 03, un enfoque común era utilizar doble comprobado de bloqueo; verificar una bandera (o el puntero en sí) para ver si el objeto podría estar sin inicializar, y solo bloquear el mutex si fuera necesario. Esto requiere algún tipo de forma no estándar de lectura atómica del puntero (o un indicador booleano asociado); muchas implementaciones usan incorrectamente un puntero simple o bool, sin garantía de que los cambios en un procesador sean visibles en otros. El código podría ser algo como esto, aunque he casi seguro que tengo algo malo:

static Singleton* getSingletonInstance() 
{ 
    if (!atomic_read(singletonInstance)) { 
     mutex_lock lock(mutex); 
     if (!atomic_read(singletonInstance)) { 
      atomic_write(singletonInstance, new Singleton); 
     } 
    } 
    return singletonInstance; 
} 

Esto es muy difícil de hacerlo bien, por lo que sugiere que no se molestan. En C++ 11, puede usar tipos atómicos y mutex estándar, si por alguna razón desea mantener la asignación dinámica de su ejemplo.

Tenga en cuenta que solo estoy hablando de la inicialización sincronizada, no del acceso sincronizado al objeto (que proporciona la versión bloqueando el mutex en el accesorio, y soltándolo más tarde a través de una función separada). Si necesita el bloqueo para acceder de forma segura al objeto en sí, obviamente no puede evitar bloquearlo en cada acceso.

2

Si tiene C++ 11 se puede hacer singletonInstance una variable atómica, a continuación, utilizar una doble comprobación de bloqueo:

if (singletonInstance == NULL) { 
    lock the mutex 
    if (singletonInstance == NULL) { 
     singletonInstance = new Singleton; 
    } 
    unlock the mutex 
} 
return singletonInstance; 
+9

Si su compilador implementa correctamente C++ 11, no necesita bloquearlo en absoluto. 'static Foo & getSingleton() {static Foo foo {}; volver foo; } 'will * simplemente funciona * en ejecución concurrente. – Xeo

+0

@Xeo - buen punto. Conviértalo en una respuesta y le daré un voto positivo, pero con un comentario que puede ser ineficaz, dependiendo de cómo el compilador gestione los bloqueos para los objetos estáticos de la función (si hay un bloqueo compartido, puede obtener contención). –

+0

Bueno, ya escribí uno, pero la pregunta no es realmente un duplicado, al menos en mi opinión: http://stackoverflow.com/a/11711991/500104. – Xeo

2

En realidad debería bloquear el único, y no la instancia. Si la instancia requiere de bloqueo, que debe ser manejada por la persona que llama (o tal vez por el propio ejemplo, dependiendo de qué tipo de una interfaz que expone) Código

actualización muestra:

#include <mutex> 

class Singleton 
{ 
    static Singleton *singletonInstance; 
    Singleton() {} 
    static std::mutex m_; 

    public: 

    static Singleton* getSingletonInstance() 
    { 
     std::lock_guard<std::mutex> lock(m_); 
     if(singletonInstance == nullptr) 
     { 
      singletonInstance = new Singleton(); 
     } 
     return singletonInstance; 
    } 
} 
2

Si utiliza subprocesos POSIX puede utilizar pthread_once_t y pthread_key_t cosas, de esta manera puede evitar el uso de mutexes por completo. Por ejemplo:

template<class T> class ThreadSingleton : private NonCopyable { 
public: 
    ThreadSingleton(); 
    ~ThreadSingleton(); 

    static T& instance(); 

private: 
    ThreadSingleton(const ThreadSingleton&); 
    const ThreadSingleton& operator=(const ThreadSingleton&) 

    static pthread_once_t once_; 
    static pthread_key_t key_; 

    static void init(void); 
    static void cleanUp(void*); 
}; 

e implementación:

template<class T> pthread_once_t ThreadSingleton<T>::once_ = PTHREAD_ONCE_INIT; 
template<class T> pthread_key_t ThreadSingleton<T>::key_; 

template<class T> 
T& ThreadSingleton<T>::instance() 
{ 
    pthread_once(&once_,init); 

    T* value = (T*)pthread_getspecific(key_); 
    if(!value) 
    { 

     value = new T(); 
     pthread_setspecific(key_,value); 
    } 
    return *value; 
} 

template<class T> void ThreadSingleton<T>::cleanUp(void* data) 
{ 
    delete (T*)data; 
    pthread_setspecific(key_,0); 
} 

template<class T> void ThreadSingleton<T>::init() 
{ 
    pthread_key_create(&key_,cleanUp); 
} 
+0

Creo que la pregunta es acerca de crear una sola instancia, no una por hilo. –

+0

Así no entendí la pregunta ... Acabo de leerlo nuevamente y creo que tienes razón. Lo siento. – piokuc

12

Como @piokuc sugirió, también se puede utilizar una vez funcionan aquí.Si usted tiene C++ 11:

#include <mutex> 

static void init_singleton() { 
    singletonInstance = new Singleton; 
} 
static std::once_flag singleton_flag; 

Singleton* getSingletonInstance() { 
    std::call_once(singleton_flag, init_singleton); 
    return singletonInstance; 
} 

Y, sí, esto va a funcionar con sensatez si el new Singleton se produce una excepción.

+1

Gracias. No estaba al tanto de la función una vez. – madu