2015-07-17 34 views
12

Ho un Singleton che è costoso per inizializzare:Qual è il modo migliore per eseguire un'inizializzazione costosa?

struct X {...}; 

const X& 
get_X() 
{ 
    static const X x = init_X(); 
    return x; 
} 

La prima volta get_X() si chiama, si può prendere centinaia di millisecondi per inizializzare la funzione di statico-locale. Ma dopo che è fatto, le cose che devo fare con la X sono relativamente veloce:

get_X().find_something_for_me(); // expensive if this is the first call 
get_X().find_something_for_me(); // now fast 

Come posso minimizzare avere un grande ritardo la prima volta che io chiamo get_X()? Ho un sacco di core ...

risposta

19

Non appena si avvia l'applicazione, e (si spera) prima che sia effettivamente necessario chiamare lo get_X(), chiamarla gratuitamente. Inoltre, in modo che la fase di inizializzazione sia più veloce, invia le tue costose inizializzazioni a thread diversi. Ad esempio:

#include <thread> 

int 
main() 
{ 
    std::thread(get_X).detach(); 
    // continue with other initialization... 
} 

Quando qualche compito è costoso come diverse centinaia di millisecondi (o più), il sovraccarico di scorporo un filo da affrontare è nel livello di rumore. E se siete su hardware multi-core (cosa non è in questi giorni?), Allora si tratta di una vittoria chiara delle prestazioni se la vostra applicazione non ha realmente bisogno di nulla da questo singleton fino a quando non viene completata la chiamata iniziale a get_X.

Note/Domande:

  • Perché detach il thread? Perché non join?

    Se si decide di join questo thread, significa che devi solo aspettare che finisca, perché non fare altre cose invece. Al termine, detach ha ripulito se stesso. Non è nemmeno necessario mantenere un handle per lo thread. Le distruzioni temporanee di std::thread, ma il thread del sistema operativo è attivo, con il completamento di get_X.

    Quando thread veniva standardizzato, c'erano punti di vista che detach ed thread s non erano solo inutili, ma pericolosi. Ma ecco un caso d'uso perfettamente sicuro e abbastanza motivante per detach ed thread s.

  • E se la mia applicazione chiama get_X() prima della detach ndr thread finiture la prima chiamata a get_X()?

    C'è un colpo di prestazioni, ma non un colpo di correttezza. L'applicazione si blocca a questa linea in get_X():

    static const X x = init_X();

    fino alla detach ndr thread è terminato l'esecuzione di esso. Quindi non esiste una gara di dati.

  • Cosa succede se la mia domanda termina prima che il detach e thread sia completo?

    Se l'applicazione termina durante la fase di inizializzazione, qualcosa è evidentemente andato male catastroficamente.Se get_X tocca qualcosa che è già stato distrutto dalla catena at_exit (che viene eseguita dopo main), accadranno cose brutte. Tuttavia, si è già in uno stato di arresto di emergenza ... un'ulteriore emergenza non è in grado di peggiorare la situazione di panico. Sei già morto. Otoh, se la tua inizializzazione è qualcosa che richiede minuti a ore, probabilmente do hanno bisogno di una comunicazione migliore su quando la tua inizializzazione è completata e procedure di spegnimento più aggraziate. In tal caso è necessario implementare la cancellazione cooperativa nelle discussioni, distaccate o meno (una cosa che la commissione std ha rifiutato di fornirti).

  • Cosa succede se la prima chiamata a get_X() genera un'eccezione?

    In questo caso, la seconda chiamata a get_X() ha la possibilità di inizializzare la funzione locale statica, presupponendo di non lasciare l'eccezione non rilevata e consentirgli di terminare il programma. Potrebbe anche buttare, o potrebbe riuscire nell'inizializzazione (cioè fino al tuo codice). In ogni caso, le chiamate a get_X() continueranno a provare a inizializzarsi, in attesa se l'inizializzazione è in corso, fino a quando qualcuno non riuscirà a farlo senza generare un'eccezione. E questo è tutto vero indipendentemente dal fatto che le chiamate arrivino o no da thread diversi o meno.

In sintesi

std::thread(get_X).detach(); 

è un buon modo per sfruttare la potenza dei vostri più core per ottenere inizializzazioni costosi indipendenti fuori del modo il più rapidamente possibile, senza compromettere la correttezza thread-sicurezza.

L'unico svantaggio è che si inizializzano i dati entro get_X() se ne avete bisogno o no. Quindi assicurati di averne bisogno prima di usare questa tecnica.


[Nota in calce] Per coloro che utilizzano Visual Studio, questo è una buona motivazione per passare a VS-2015. Prima di questa versione VS non implementa la funzione locale statica thread-safe.

+2

più uno, perché ho imparato qualcosa di nuovo. Beh, probabilmente ne avrei fatto un po 'dopo aver incontrato il problema. Ma, bello sapere. :) –

+1

Se l'inizializzazione può essere lanciata, potrebbe essere necessario avvolgerla in un tentativo/catch per evitare la 'terminazione'? –

+1

"non c'è traccia di dati" - purché si stia utilizzando un compilatore che ha effettivamente implementato la sicurezza del thread C++ 11 per l'inizializzazione statica della funzione locale. – TheUndeadFish

2

Se non ti dispiace il ritardo all'avvio dell'applicazione, si potrebbe fare x come membro privato statico della classe, come questo

#include <iostream> 

class X { 
public: 
    static X const& get_x(); 
private: 
    static X const x; 

    X() 
    { 
     std::cout << "X init" << std::endl; 
    } 
}; 

int main() 
{ 
    std::cout << "Enter main" << std::endl; 
    X::get_x(); 
    return 0; 
} 

X const X::x; 

X const& X::get_x() 
{ 
    return X::x; 
} 
Problemi correlati