2010-08-29 21 views
29
class Unit { 
    private readonly string name; 
    private readonly double scale; 

    public Unit(string name, double scale) { 
     this.name = name; 
     this.scale = scale, 
    } 

    public string Name { get { return name; } } 
    public string Scale { get { return scale; } } 

    private static Unit gram = new Unit("Gram", 1.0); 

    public Unit Gram { get { return gram; } } 
} 

Più thread hanno accesso a Unit.Gram. Perché è ok per più thread contemporaneamente leggere Unit.Gram.Title?Perché gli oggetti immutabili sono thread-safe?

La mia preoccupazione è che si riferiscono alla stessa posizione di memoria. Un thread inizia a leggere quella memoria, quindi non è "bloccato" allora? .NET gestisce la sincronizzazione per questa sezione critica sottostante? O sbaglio nel pensare che la lettura simultanea abbia bisogno di sincronizzazione?

+0

"La mia preoccupazione è che si riferiscono alla stessa posizione di memoria." Sono loro? Può essere. Non è la tua preoccupazione, è la preoccupazione della VM. La lettura simultanea non ha bisogno di sincronizzazione, perché dovrebbe? –

+2

* "perché dovrebbe" * >> non è la domanda qui? Se leggo lo stesso libro che legge la mia ragazza, allo stesso tempo, finiamo sempre per litigare. So che non è un problema nei computer però (due thread che leggono allo stesso tempo non accade, l'architettura della CPU si occupa di questo), ma trovo una domanda molto legittima :) – Abel

+0

Leggere un libro implica uno stato mutabile (pagina corrente, fulmine, ecc.). Non stai litigando per il libro, ma per l'elaborazione. – sisve

risposta

11

Penso che la tua domanda risulta non essere di circa thread-sicurezza o immutabilità ma riguardo ai dettagli (molto) di basso livello dell'accesso alla memoria.

E questo è un argomento pesante ma la risposta breve è: Sì, due thread (e più importante, 2+ CPU) possono leggere (e/o scrivere) lo stesso pezzo di memoria simultaneamente.

E fintanto che il contenuto di tale area di memoria è immutabile, tutti i problemi sono risolti. Quando può cambiare, c'è tutta una serie di problemi, la parola chiave volatile e la classe Interlocked sono alcuni degli strumenti che usiamo per risolverli.

+1

A un livello basso, come dici tu, credo che l'accesso simultaneo alla memoria non avvenga. I processori non possono accedere alla stessa memoria nello stesso momento, a meno che la memoria non sia memorizzata nella cache. In caso contrario, è necessario utilizzare il bus di memoria, che può essere utilizzato da un solo processore alla volta. Questo blocca i processori e limita il guadagno di prestazioni che potrebbero dare molti processori (l'accesso alla memoria è più lento rispetto alla CPU). Un rimedio è il [concetto o architettura NUMA] (http://en.wikipedia.org/wiki/Non-Uniform_Memory_Access), in cui vengono utilizzati bus separati per accedere (ancora separati) alle posizioni di memoria: – Abel

+0

@Abel, hai ragione ma l'ho chiamato "il racconto": per quanto noi (programmatori) possiamo dire, è simultaneo. Anche per programmatori di linguaggio assembly. –

+0

Credo che tu abbia ragione. Ma la ramificazione della sincronizzazione della memoria di lettura gestita in modo trasparente è, esattamente, immutabilità ergo thread-safety. La risposta effettiva sembra essere quella in quanto ** viene gestita sotto **, e interrogarsi sul fatto che sia gestita non è poi così stupida. :) – randomguy

4

Se l'oggetto è immutabile, il suo stato non cambierà mai. Quindi le preoccupazioni dei dati obsoleti vanno fuori dalla finestra. La lettura del thread non è mai bloccata, quindi non è un problema (deadlock)

57

Cosa rende un oggetto non thread sicuro? Un oggetto non è thread-safe se il valore/stato di quell'oggetto può cambiare mentre un thread lo sta leggendo. Ciò accade generalmente se un secondo thread modifica il valore di questo oggetto mentre il primo thread lo sta leggendo.

Un oggetto immutabile, per definizione, non può modificare il valore/stato. Poiché ogni volta che leggi un oggetto immutabile ha lo stesso valore/stato, puoi avere un numero qualsiasi di thread che legge quell'oggetto senza preoccupazioni.

+15

È possibile aggiungere che quando un oggetto immutabile è inizializzato, lo stato * fa * cambia, ma CLR garantisce che questo processo avvenga in modalità thread-safe (cioè, non è possibile che un altro thread tenti di leggere mentre il processo init non è finito nel ctor o cctor). – Abel

+0

@Abel: Ritengo che il tuo commento sia corretto, ma potresti portare un riferimento allo standard di lingua che supporta il tuo reclamo?In particolare, perché è impossibile che le scritture nel costruttore vengano spostate oltre la pubblicazione dell'oggetto? – Vlad

11

Le letture simultanee non devono essere sincronizzate con. Poiché la sincronizzazione è necessaria solo per gli scrittori (o lettori e almeno un writer), gli oggetti non modificabili non richiedono la sincronizzazione e sono quindi compatibili con il thread.

3

Le letture contemporanee non richiedono la sincronizzazione nella maggior parte dei casi (le eccezioni sono cose come l'I/O mappato in memoria in cui la lettura da un determinato indirizzo può causare effetti collaterali).

Se le letture simultanee richiedevano la sincronizzazione, sarebbe quasi impossibile scrivere codice multi-threadato in modo utile. Ad esempio, per eseguire una parte di codice, il processore deve leggere il flusso di istruzioni, come si scriverà una funzione di blocco se la funzione stessa deve proteggere contro se stessa eseguita contemporaneamente?

+2

Per essere onesti, a un certo livello, l'hardware ha a che fare con questi problemi. Ma è fatto in hardware e non è nemmeno visibile al sistema operativo, per non parlare dello scrittore di applicazioni .NET. – siride

+0

@siride, in realtà penso che dovrei accettare il tuo commento come risposta perché in realtà risponde alla domanda. ** La sincronizzazione viene gestita sotto. ** Dopo di che è facile capire l'immutabilità IMHO. – randomguy

+1

@randomguy, beh, l'hardware _some_ deve affrontare questi problemi ad alcuni livelli, ma introduce anche i bisogni in primo luogo ad alcuni livelli. Se vuoi dire che è "gestito sotto" quello che devi dire è "su alcuni progetti di computer in cui letture concorrenti potrebbero causare problemi a causa di scelte di progettazione, la possibilità che il software venga influenzato da questo viene eliminata attraverso vari meccanismi, altri design sono non influenzato da letture concorrenti, e in altri ancora letture concorrenti sono impossibili. " –

1

Memory Management Units sono la parte del processore che gestisce la lettura della memoria. Se ne hai più di una, una volta in una luna blu, 2 di loro potrebbero provare a leggere la stessa posizione di memoria nella stessa dozzina di nanosecondi, ma nessun problema risulta vedere quando ottengono la stessa risposta.

1

Oltre alle eccezioni come la memoria mappata per i driver, ad esempio, non esiste alcun problema per due thread che leggono contemporaneamente lo stesso indirizzo di memoria. Un problema può sorgere quando un thread esegue alcuni scrivendo i dati. In questo caso, è possibile impedire ad altri thread di leggere quell'oggetto/dati.

Ma il problema non è dovuto al simultaneità degli scritti (a livello elettronico più basso si verificano uno dopo l'altro in ogni caso), il problema è piuttosto che l'oggetto/set di dati può perdere la loro consistenza. Di solito si utilizza uno section critic per isolare del codice che potrebbe non essere letto/scritto contemporaneamente da altri thread.

Ci sono molti esempi in Rete, ma si consideri il seguente, con price è un membro privato di una classe, dire prodotto, che ha anche 2 metodi

public void setPrice(int value) { 
    price = value; 
    // -- point CRITIC -- 
    price += TAX; 
} 

public int getPrice() { 
    return price; 
} 

setPrice (v) fissa il prezzo di un prodotto a v, e aggiustarlo con IVA (il programma dovrebbe avere value += TAX; price = value ma questo non è il punto qui :-)

Se il thread A scrive 100, e TAX è (fisso) 1, il prezzo del prodotto sarà finalmente essere impostato su 101. Ma cosa succede se il thread B legge il prezzo tramite getPrice() mentre il thread A è point CRITIC? Il prezzo restituito a B salterà la TASSA e sarà sbagliato.

setPrice() dovrebbe all'interno di una sezione critica (lock), per evitare l'accesso all'oggetto durante l'impostazione del prezzo

lock(this) 
    { 
     price = value; 
     price += TAX; 
    } 
2

La lettura della stessa posizione di memoria può essere eseguita in un ciclo della CPU solo da una filettatura specifica. L'ordine di lettura in questo caso non ha importanza poiché il valore sottostante non cambia. Pertanto non vi è alcuna possibilità che una lettura sia incoerente, quindi in questo caso non c'è bisogno di sincronizzazione a nessun livello.

+0

Penso che questo risponda anche alla domanda molto bene. Ti piacerebbe espandere questa risposta? – randomguy

+0

In realtà, 2 CPU potrebbero leggere lo stesso indirizzo nello stesso clock-tick (e vede valori diversi), perché ognuno legge dalla propria cache –

+0

Questo è corretto, ma ai fini della domanda e della semplificazione ho dovuto assumere una singola situazione CPU. Tuttavia, l'unica parte che cambia in una situazione multi-CPU è la possibilità di memorizzazione nella memoria, ma il valore è sempre lo stesso Anche se il grammo variabile è dichiarato statico, non esiste un "setter" per esso ed è inizializzato al valore 1.0 una volta, pertanto è immutabile e non richiede alcuna sincronizzazione del programma. – venky

Problemi correlati