Domanda

Quindi, ho visto un sacco di articoli ora, sostenendo che il C ++ interblocco ricontrollato, comunemente usato per prevenire più thread dal tentativo di inizializzare un Singleton pigramente creato, è rotto. doppio codice di blocco normale controllato legge come questo:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!instance)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;
        }

        return *instance;
    }
};

Il problema apparentemente è la linea assegnando esempio - il compilatore è libero di allocare l'oggetto e quindi assegnare il puntatore ad esso, o per impostare il puntatore dove verrà allocata, quindi allocare. Quest'ultimo caso rompe il linguaggio - un thread può allocare la memoria e assegnare il puntatore ma non eseguiti costruzione della singleton prima che venga messo a dormire - poi il secondo filo vedrà che l'istanza non è nullo e cercare di restituirla , anche se non è stato ancora costruito.

I ha visto un suggerimento di utilizzare un filo booleana locale e verificare che, invece di instance. Qualcosa di simile a questo:

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;
    static boost::thread_specific_ptr<int> _sync_check;

public:
    static singleton & instance()
    {
        static singleton* instance;

        if(!_sync_check.get())
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            // Any non-null value would work, we're really just using it as a
            // thread specific bool.
            _sync_check = reinterpret_cast<int*>(1);
        }

        return *instance;
    }
};

In questo modo ogni filo finisce controllare se l'istanza è stata creata una volta, ma si ferma dopo che, che comporta qualche calo di prestazioni ma non così male come bloccaggio ogni chiamata. Ma cosa succede se abbiamo usato solo un bool statica locale:?

class singleton {
private:
    singleton(); // private constructor so users must call instance()
    static boost::mutex _init_mutex;

public:
    static singleton & instance()
    {
        static bool sync_check = false;
        static singleton* instance;

        if(!sync_check)
        {
            boost::mutex::scoped_lock lock(_init_mutex);

            if(!instance)           
                instance = new singleton;

            sync_check = true;
        }

        return *instance;
    }
};

Perché non sarebbe questo lavoro? Anche se sync_check dovesse essere letto da un thread quando sta assegnato un altro valore immondizia sarà ancora diverso da zero e così vero. articolo di questo Dr. Dobb sostiene che si deve chiudere, perché non sarai mai vincere una battaglia con il compilatore su istruzioni di riordino. Il che mi fa pensare che non deve lavorare per qualche ragione, ma non riesco a capire perché. Se i requisiti sui punti della sequenza sono come perdere come l'articolo di Dr. Dobb mi fa credere, io non capisco perché qualsiasi codice dopo il blocco non poteva essere riordinati per essere prima la serratura. Il che renderebbe C ++ multithreading periodo rotto.

Credo che avrei potuto vedere il compilatore di essere autorizzato a riordinare specificamente sync_check essere prima la serratura perché è una variabile locale (ed anche se è statico non stiamo restituendo un riferimento o puntatore ad esso) - ma allora questo potrebbe ancora essere risolto facendo un membro statico (efficacemente globale) invece.

Quindi sarà questo lavoro o no? Perché?

È stato utile?

Soluzione

Il tuo correzione non risolve nulla dal momento che le operazioni di scrittura per sync_check e l'istanza può essere fatto fuori ordine sulla CPU. Come esempio immaginare le prime due chiamate esempio accadono approssimativamente nello stesso tempo su due differenti CPU. Il primo thread acquisire il blocco, inizializzare il puntatore e sync_check true, in questo ordine, ma il processore può modificare l'ordine delle scritture nella memoria. D'altra CPU, allora è possibile che il secondo filo per controllare sync_check, vedere che è vero, ma esempio non può ancora essere scritti nella memoria. Vedere lockless per Xbox 360 e Microsoft Windows per i dettagli.

La soluzione sync_check thread specifico si parla dovrebbe funzionare allora (supponendo che si inizializza il puntatore a 0).

Altri suggerimenti

C'è qualche grande lettura di questo (anche se è .net / c # oriented) qui: http://msdn.microsoft.com/en-us/magazine/cc163715.aspx

Che cosa si riduce a è che è necessario essere in grado di dire la CPU che non è possibile riordinare la tua legge / scrive per questo accesso variabili (da quando il Pentium originale, la CPU può riordinare alcune istruzioni se si pensa che la logica sarebbe inalterato), e che ha bisogno di assicurarsi che la cache è coerente (non dimenticare che - noi sviluppatori arrivare a pretendere che tutta la memoria è solo una risorsa piatta, ma in realtà, ogni core della CPU è la cache, un po ' non condivisa (L1), alcuni potrebbero essere condivisa volte (L2)) - tua initizlization potrebbe scrivere RAM principale, ma un altro nucleo potrebbe avere il valore inizializzato nella cache. Se non si dispone di alcun semantica di concorrenza, la CPU non può sapere che si tratta di cache è sporca.

Non so lato C ++, ma in .net, si dovrebbe designare la variabile come volatile, al fine di proteggere l'accesso ad essa (o si dovrebbe utilizzare la memoria, lettura / scrittura metodi di barriera in System.Threading).

Per inciso, ho letto che in .net 2.0, doppia chiusura controllate è garantito il funzionamento senza variabili "volatili" (per tutti i lettori .net là fuori) - che non ti aiuta con il codice C ++ .

Se si vuole essere sicuri, è necessario fare lo ++ equivalente c di marcare una variabile come volatile in C #.

"Quest'ultimo caso rompe il linguaggio -. Due fili possono finire per creare il singleton"

Ma se ho capito correttamente il codice, il primo esempio, si controlla se l'istanza è già esistente (potrebbe essere eseguito da più thread allo stesso tempo), se lo fa, non un filo get a bloccarlo e crea l'istanza - solo un thread può eseguire la creazione in quel momento. Tutti gli altri thread vengono bloccati e aspetteranno.

Una volta che l'istanza viene creato e il mutex è sbloccato il prossimo thread in attesa si blocca mutex, ma non cercherà di creare nuova istanza perché il controllo non riesce.

La prossima volta che la variabile di istanza è selezionata verrà impostata in modo che nessun discussioni cercherà di creare nuova istanza.

Non sono sicuro circa il caso in cui un thread sta assegnando nuovo puntatore caso a caso, mentre un altro thread controlla la stessa variabile - ma credo che sarà gestito in modo corretto, in questo caso

.

Mi sto perdendo qualcosa qui?

Ok non sono sicuro circa il riordino delle operazioni, ma in questo caso sarebbe alterando logica in modo non mi aspetterei che accada -. Ma io non sono esperto su questo argomento

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top