2012-04-25 18 views
9

Ho un thread che gira finché un int modificato da un altro thread è un determinato valore.Ho bisogno che questo campo sia volatile?

int cur = this.m_cur; 
while (cur > this.Max) 
{ 
    // spin until cur is <= max 
    cur = this.m_cur; 
} 

Questo file.m_cur deve essere dichiarato volatile perché funzioni? È possibile che questo giri per sempre a causa dell'ottimizzazione del compilatore?

+9

Effettua l'int una proprietà e segnala il thread, (autoResetEvent, forse), nel metodo setter. Problema aggirato, utilizzo della CPU ridotto, dubbio volatile rimosso. –

+0

Questa è in genere una cattiva idea tranne in alcuni rari casi; ti capita di sapere quanti microsecondi al massimo ti aspetti di girare? –

+0

Il ciclo della CPU durante la lettura di 'cur' che viene scritto da un altro thread non sarà in grado di rilevare la fine fuori limite se il thread di polling non è in esecuzione quando il thread setter scrive un valore fuori limite. Se è stato preimpostato su un box sovraccarico, dovrà aspettare un mezzo-a-quantum medio prima di rilevare il parametro out-of-range.Se il cur diventa di nuovo nel raggio di copertura mentre il poller non è in esecuzione, non rileverà affatto la condizione di fuori portata. –

risposta

12

Sì, questo è un requisito difficile. Il compilatore just-in-time è autorizzato a memorizzare il valore di m_cur in un registro del processore senza aggiornarlo dalla memoria. Il jitter x86 infatti lo fa, il jitter x64 non lo fa (almeno l'ultima volta che l'ho guardato).

volatile La parola chiave è necessaria per sopprimere questa ottimizzazione.

Volatile significa qualcosa di completamente diverso sui core Itanium, un processore con un modello di memoria debole. Sfortunatamente è ciò che ha reso la libreria MSDN e la specifica del linguaggio C#. Resta da vedere cosa significherà su un nucleo ARM.

+4

Mi piace come hai risposto alla sua domanda, invece di dire "fallo in un altro modo" come piace a molti. A seconda delle sue esigenze, un segnale potrebbe essere molto più efficiente, ma la sua domanda non era "... o cosa è meglio?" Inoltre, sono sicuro che molti hanno imparato qualcosa di nuovo qui. Grazie. – payo

+0

+1: A proposito, siamo sicuri che le proprietà impediscono al compilatore JIT di eseguire l'ottimizzazione del sollevamento. La proprietà sarebbe semplice, quindi probabilmente sarebbe stata inserita per prima. Suppongo che sia possibile fare un'ottimizzazione in 2 fasi: inline-lift. Non ho visto nulla nelle specifiche che lo precludessero, ma forse non mi sembrava così difficile. –

+1

@Brian - L'ho scoperto rimanendo perplesso riguardo alle proprietà utilizzate nei casi in cui sarebbe volatile, ma senza alcuna sincronizzazione. BackgroundWorker.CancellationPending è un buon esempio, un bool. Questo non è descritto da nessuna parte che io conosca, la semantica di * volatile * è terribilmente scarsamente documentata. Lo sono sempre stati, anche prima di .NET. –

4

Il blog sotto ha alcuni dettagli affascinanti sul modello di memoria in C#. In breve, sembra più sicuro usare la parola chiave volatile.

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

Dal blog di sotto

class Test 
{ 
    private bool _loop = true; 

    public static void Main() 
    { 
     Test test1 = new Test(); 

     // Set _loop to false on another thread 
     new Thread(() => { test1._loop = false;}).Start(); 

     // Poll the _loop field until it is set to false 
     while (test1._loop == true) ; 

     // The loop above will never terminate! 
    } 
} 

Ci sono due possibili modi per ottenere il ciclo while per terminare: utilizzare un blocco per proteggere tutti gli accessi (letture e scritture) al _loop field Contrassegna il campo _loop come volatile Ci sono due motivi per cui una lettura di un campo non volatile può osservare un valore non aggiornato: ottimizzazioni del compilatore e ottimizzazioni del processore.

+1

Sembra addirittura più sicuro non utilizzare affatto i loop di polling. –

+0

Vero per la maggior parte dei casi. Anche se immagino che ci siano dei motivi per bloccare il blocco troppo, come evitare il cambio di contesto di produrre un thread. –

+0

Il problema di alterare i commutatori di contesto è che non è sempre facilmente possibile. Inoltre, il codice OP non è uno spinlock clsssic con bandiera booleana. Se la scatola è sovraccaricata perché ci sono più thread pronti rispetto ai core, il thread di polling potrebbe essere preempito e non funzionare per un po '. Durante questo periodo, non può rilevare che 'cur' sta uscendo fuori limite. Se la curva ritorna entro il limite prima dell'esecuzione del poller, la condizione di fuori limite è stata completamente ignorata. –

0

Dipende da come m_cur viene modificato. Se sta usando una normale istruzione di assegnazione come m_cur--;, allora deve essere volatile. Tuttavia, se viene modificato utilizzando una delle operazioni Interlocked, ciò non avviene perché i metodi di Interlocked inseriscono automaticamente una barriera di memoria per garantire che tutti i thread ottengano il memo.

In generale, l'opzione preferibile è l'utilizzo di Interlock per modificare valori atomici condivisi tra thread. Non solo si prende cura della barriera di memoria per te, ma tende anche ad essere un po 'più veloce rispetto ad altre opzioni di sincronizzazione.

Detto questo, come altri hanno detto che i cicli di polling sono enormemente dispendiosi. Sarebbe meglio mettere in pausa il thread che deve aspettare, e lasciare che chiunque modifichi m_cur si faccia carico di svegliarlo quando arriverà il momento. Sia Monitor.Wait() and Monitor.Pulse() e AutoResetEvent potrebbero essere adatti all'attività, a seconda delle esigenze specifiche.

+0

La soluzione del ciclo di polling non funziona in modo affidabile, almeno, non su una scatola sovraccaricata. Il thread setter può scrivere un 'cur' fuori range mentre il poller non è in esecuzione. Se "cur" ritorna di nuovo nell'intervallo prima dell'esecuzione del polling, la condizione di fuori portata non viene rilevata affatto. –

Problemi correlati