2012-09-06 15 views
11

Ho letto molte domande considerando il blocco a doppio controllo sicuro per thread (per singleton o init pigro). In alcuni thread, la risposta è che il pattern è completamente infranto, altri suggeriscono una soluzione.C++ 11: blocco doppio controllo sicuro per l'inizializzazione pigra. Possibile?

Quindi la mia domanda è: esiste un modo per scrivere un modello di blocco a doppio controllo completamente protetto da thread in C++? Se è così, com'è?

Possiamo assumere C++ 11, se questo rende le cose più facili. Per quanto ne so, C++ 11 ha migliorato il modello di memoria che potrebbe produrre i miglioramenti necessari.

So che è possibile in Java rendendo volatile la variabile a doppio controllo controllata. Dal momento che C++ 11 ha preso in prestito grandi parti del modello di memoria da quello di Java, quindi penso che potrebbe essere possibile, ma come?

+5

Se è possibile utilizzare C++ 11, ignorare l'intera attività di blocco con doppia verifica e utilizzare le variabili locali statiche o 'std :: call_once'. –

+0

I locals statici sono inizializzati pigramente? E su 'call_once': come fa a garantire che la chiamata una volta non scriva il riferimento non completamente creato alla variabile? – gexicide

+3

sì, i locals statici sono inizializzati pigramente in modo thread-safe. E 'call_once' garantisce che il soggetto venga chiamato sempre una volta sola; e che nessun'altra chiamata a 'call_once' ritorna prima che quello che esegue effettivamente la funzione ritorni (puoi leggere di più qui http://en.cppreference.com/w/cpp/thread/call_once). Come lo è fino all'implementazione. Queste due cose esistono fondamentalmente, quindi non ti interessa preoccuparti di scrivere più bug implementazioni di blocco a doppio controllo. –

risposta

16

è sufficiente utilizzare un variabile locale statica per Singletons pigramente inizializzati, in questo modo:

MySingleton* GetInstance() { 
    static MySingleton instance; 
    return &instance; 
} 

L'(C++ 11) di serie garantisce già che le variabili statiche vengono inizializzate in modo threadsafe e sembra probabile che la l'implementazione di questo almeno altrettanto robusto e performante di qualsiasi cosa tu possa scrivere da solo.

La threadsafety di inizializzazione può essere trovato nel §6.7.4 del (C++ 11) Campione:

Se il controllo entra nella dichiarazione contemporaneamente mentre viene inizializzata la variabile, l'esecuzione concorrente deve attendere il completamento dell'inizializzazione.

+11

Questo è folle. Perché, oh, perché, potresti mai restituire un puntatore se può ** mai ** essere "null"? –

+1

@MatthieuM .: Principalmente perché rende le persone meno inclini a copiare l'oggetto sottostante. Ovviamente un singleton ben progettato ** non dovrebbe avere un costruttore di copia, ma comunque. Davvero non vedo come il ritorno per riferimento rispetto al ritorno per valore sia importante in quel caso. Perciò difficilmente lo chiamerei pazzo. – Grizzly

+2

@Grizzly: se un oggetto non deve essere copiato, spetta all'oggetto applicarlo. Se un oggetto deve avere un'istanza accessibile globalmente, dovrebbe esserci una funzione per gestirla (come la tua). Queste due cose sono separate e non c'è motivo di combinarle. Questo è il motivo per cui il modello di Singleton è stupido. – GManNickG

3

Dato che si desidera visualizzare un'implementazione C++ 11 DCLP valida, eccone uno.

Il comportamento è completamente thread-safe e identico a GetInstance() nella risposta di Grizzly.

std::mutex mtx; 
std::atomic<MySingleton *> instance_p{nullptr}; 

MySingleton* GetInstance() 
{ 
    auto *p = instance_p.load(std::memory_order_acquire); 

    if (!p) 
    { 
     std::lock_guard<std::mutex> lck{mtx}; 

     p = instance_p.load(std::memory_order_relaxed); 
     if (!p) 
     { 
      p = new MySingleton; 
      instance_p.store(p, std::memory_order_release); 
     } 
    } 

    return p; 
} 
+0

In pratica, dovresti dichiarare le tue variabili 'mtx' e' instance_p' come globali al di fuori della funzione, piuttosto che come statiche all'interno della funzione, poiché altrimenti stai pagando il prezzo per i controlli interni del compilatore sull'inizializzazione di 'mtx' e' instance_p' su _every_ call, sconfiggendo il punto di doppio controllo (dal momento che anche le prestazioni potrebbero dichiarare il singleton come statico). – BeeOnRope

+0

@BeeOnRope Punto valido .. Ho apportato questa modifica. – LWimsey

+0

Si potrebbe voler aggiungere che in pratica non si vuole mai scrivere questo codice. La risposta di Grizzly è più concisa e il compilatore potrebbe inserire più magia in futuro per renderlo più veloce di questo codice. – gexicide

Problemi correlati