Domanda

Quando scrivo un valore in un campo, quali garanzie posso ottenere per quanto riguarda quando il nuovo valore verrà salvato nella memoria principale? Ad esempio, come faccio a sapere che il processore non tenere il nuovo valore nel suo nascondiglio privato, ma ha aggiornato la memoria principale?
Un altro esempio:

int m_foo;

void Read() // executed by thread X (on processor #0)
{
   Console.Write(m_foo);
}

void Write() // executed by thread Y (on processor #1)
{
   m_foo = 1;
}

C'è una possibilità che dopo la Write () è stato terminato l'esecuzione, alcuni altri esegue filo Read () , ma in realtà vedranno "0" come il valore attuale? (Dal momento che forse la scrittura precedente m_foo non era ancora arrossato?).
Che tipo di primitive (accanto serrature) sono a disposizione per garantire le operazioni di scrittura dei arrossate?


Modifica
Nel codice di esempio che ho usato, la scrittura e lessi sono messi a metodo diverso. Non riguardano solo Thread.MemoryBarrier Reording istruzioni che esistono nello stesso ambito?

Inoltre, supponiamo che non saranno inline dal JIT, come posso fare in modo che il valore scritto per m_foo non verranno memorizzati in un registro, ma per la memoria principale? (O quando m_foo viene letto, esso non ottenere un valore precedente dalla cache della CPU).

E 'possibile raggiungere questo obiettivo senza usare serrature o la parola 'volatili'? (Anche, diciamo che non sto usando tipi primitivi, ma una parola di dimensioni struct [così volatile non può essere applicato] .)

È stato utile?

Soluzione

volatile e prese sono già stati menzionati, hai chiesto primitive, un'aggiunta alla lista è quello di utilizzare Thread.MemoryBarrier() davanti ai vostri scrive o legge. Questo garantisce non riordino è fatto della memoria scrive e legge.

Questo sta facendo "a mano" ciò che lock, Interlocked e volatile può fare automaticamente la maggior parte del tempo. Si potrebbe utilizzare questo come una sostituzione completa per qualsiasi altra tecnica, ma è probabilmente il percorso più difficile da percorrere, e così dice MSDN:

  

"E 'difficile costruire corretti programmi multithread utilizzando   MemoryBarrier. Per la maggior parte degli scopi, la   C # dichiarazione di blocco, il Visual Basic   dichiarazione SyncLock, e le modalità di   la classe Monitor fornire più facile e   modi meno soggetto a errori da sincronizzare   accessi alla memoria. Si consiglia di   usarli al posto di MemoryBarrier. "

Come utilizzare MemoryBarrier

Un esempio molto fine sono le implementazioni di VolatileRead e VolatileWrite, che entrambi usano internamente MemoryBarrier. La regola di base del pollice da seguire è: quando si legge una variabile, posizionare una barriera di memoria dopo di lettura. Quando si scrive il valore, la barriera di memoria deve venire prima la scrittura.

Nel caso in cui hai dubbi se questo è meno efficiente allora lock, considerare che il blocco non è altro poi "pieno di scherma", nel senso che pone una barriera di memoria prima e dopo il blocco di codice (Monitor ignorando per un momento). Questo principio è ben spiegato in questo eccellente articolo definitivo sulle filettature, blocco, barriere volatili e di memoria Albahari .

Da riflettore:

public static void VolatileWrite(ref byte address, byte value)
{
    MemoryBarrier();
    address = value;
}

public static byte VolatileRead(ref byte address)
{
    byte num = address;
    MemoryBarrier();
    return num;
}

Altri suggerimenti

Se si vuole garantire è scritto prontamente e in ordine, poi segnarlo come volatile , o (con più dolore) utilizzare Thread.VolatileRead / Thread.VolatileWrite (non è un'opzione attraente e facile da perdere uno, il che rende inutile).

volatile int m_foo;

In caso contrario, si ha praticamente alcuna garanzia di qualsiasi cosa (non appena si parla di più thread).

Si potrebbe anche voler guardare bloccaggio ( Monitor ) o Interlocked , che raggiunga lo stesso effetto finché stesso approccio viene utilizzato da ogni accesso (cioè tutti lock , o tutti Interlocked, ecc).

Fino a quando non si utilizza alcuna sincronizzazione si ha alcuna garanzia che un thread in esecuzione su un processore vede le modifiche apportate da un altro thread in esecuzione su un altro processore. Questo perché il valore potrebbe essere memorizzato nella cache in cache della CPU o in un registro della CPU.

Pertanto è necessario sia marcare la variabile come volatile. Che creerà un 'Succede-Before'-realation tra legge uno scrittura.

Non è un problema di cache del processore. Scrive di solito sono pass-through (scrive andare sia di memorizzare nella cache e la memoria principale) e tutte le letture accederà alla cache. Ma ci sono molte altre cache sulla strada (linguaggio di programmazione, librerie, sistema operativo, I / O tamponi, ecc). Il compilatore può anche scegliere di mantenere una variabile in un registro processore e di non scrivere nella memoria principale (che è quello che l'operatore volatile è progettato per, evitare valore memorizzazione nel registro quando può essere una memoria di I / O mappato).

Se si dispone di più processi o più thread e la sincronizzazione è un problema è necessario farlo esplicitamente, ci sono molti modo per farlo a seconda del caso d'uso.

Per un singolo programma filettato, non mi interessa, il compilatore farà quello che deve e legge avrà accesso a ciò che è stato scritto.

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