2012-06-12 8 views
6

Stavo leggendo il post tips and tricks e ho pensato di provare alcune delle cose di C# che non avevo mai fatto prima. Pertanto, il seguente codice non ha alcun scopo reale, ma è solo una 'funzione di test' per vedere cosa succede.C# ThreadStatic + membri volatili che non funzionano come previsto

In ogni caso, ho due campi privati ​​statici:

private static volatile string staticVolatileTestString = ""; 
[ThreadStatic] 
private static int threadInt = 0; 

Come potete vedere, sto testando ThreadStaticAttribute e la volatilità parola chiave .

Comunque, ho un metodo di prova che assomiglia a questo:

private static string TestThreadStatic() { 
    // Firstly I'm creating 10 threads (DEFAULT_TEST_SIZE is 10) and starting them all with an anonymous method 
    List<Thread> startedThreads = new List<Thread>(); 
    for (int i = 0; i < DEFAULT_TEST_SIZE; ++i) { 
     Thread t = new Thread(delegate(object o) { 
      // The anon method sets a newValue for threadInt and prints the new value to the volatile test string, then waits between 1 and 10 seconds, then prints the value for threadInt to the volatile test string again to confirm that no other thread has changed it 
      int newVal = randomNumberGenerator.Next(10, 100); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
      threadInt = newVal; 
      Thread.Sleep(randomNumberGenerator.Next(1000, 10000)); 
      staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " finished: " + threadInt; 
     }); 
     t.Start(i); 
     startedThreads.Add(t); 
    } 

    foreach (Thread th in startedThreads) th.Join(); 

    return staticVolatileTestString; 
} 

Quello che mi aspetto di vedere tornato da questa funzione è un output come questo:

thread 0 setting threadInt to 88 
thread 1 setting threadInt to 97 
thread 2 setting threadInt to 11 
thread 3 setting threadInt to 84 
thread 4 setting threadInt to 67 
thread 5 setting threadInt to 46 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

Tuttavia, ciò che sto ottenendo è questa:

thread 0 setting threadInt to 88 
thread 4 setting threadInt to 67 
thread 6 setting threadInt to 94 
thread 7 setting threadInt to 60 
thread 8 setting threadInt to 11 
thread 9 setting threadInt to 81 
thread 5 finished: 46 
thread 2 finished: 11 
thread 4 finished: 67 
thread 3 finished: 84 
thread 9 finished: 81 
thread 6 finished: 94 
thread 7 finished: 60 
thread 1 finished: 97 
thread 8 finished: 11 
thread 0 finished: 88 

la seconda 'meta' l'uscita è come previsto (che suppongo mezzi ° al campo ThreadStatic funziona come pensavo), ma sembra che alcune delle uscite iniziali siano state "saltate" dalla prima "metà".

Inoltre, i thread nel primo "mezzo" sono fuori servizio, ma ho capito che un thread non viene eseguito immediatamente non appena si chiama Start(); ma invece i controlli interni del sistema operativo inizieranno i thread come meglio crede. EDIT: No non stanno, in realtà, ho solo pensato che fossero perché il mio cervello manca i numeri consecutivi


Quindi, la mia domanda è: cosa sta andando male a causare me di perdere un paio di righe nel prima 'metà' dell'output? Ad esempio, dov'è la riga "thread 3 setting threadInt a 84"?

+0

Eseguo il codice un paio di volte e ottengo sempre l'output previsto ... –

+0

Scommetterei che da qualche parte lungo la linea, la chiamata a + = sulla stringa sta ottenendo il valore prima che il thread precedente l'abbia impostata e il nuovo thread lo sta sovrascrivendo con il nuovo valore (quindi il thread 1 ha saltato l'output). – Charleh

+0

@DannyChen Sono su un quad-core e non è l'unico codice in esecuzione nell'applicazione; non so se uno di questi avrebbe senso. Posso caricare l'intero codice da qualche parte (sono solo due file .cs) se lo desideri? – Xenoprimate

risposta

7

I thread sono in esecuzione allo stesso tempo. Quello che succede concettualmente è questo:

staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
  1. Discussione 1 legge staticVolatileTestString
  2. Discussione 2 legge staticVolatileTestString
  3. Discussione 3 recita staticVolatileTestString
  4. Discussione 1 aggiunge il roba e scrive staticVolatileTestString indietro
  5. Thread 2 aggiunge il contenuto e scrive staticVolatileTestString indietro
  6. Thread 3 appende il contenuto e scrive staticVolatileTestString indietro

Ciò causa la perdita delle linee. Volatile non aiuta qui; l'intero processo di concatenazione della stringa non è atomico.È necessario utilizzare un blocco intorno a tali operazioni:

private static object sync = new object(); 

lock (sync) { 
    staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
} 
+0

Sì, la stessa cosa che ho detto solo molto meglio spiegato: D haha ​​+1 – Charleh

2

MSDN descrive ciò che la parola volatile fa here:

La parola chiave volatile indica che un campo potrebbe essere modificato da più thread contemporaneamente esecuzione. I campi dichiarati volatile non sono soggetti alle ottimizzazioni del compilatore che presuppongono l'accesso a da un singolo thread. Ciò garantisce che il valore più aggiornato sia presente sul campo in ogni momento.

Ciò significa, nel tuo esempio, ciò che accade è circa questo (questo può variare di volta in volta, dipende dal scheduler):

  • filo 0 sta leggendo la stringa di staticVolatileTestString
  • filo 0 viene aggiungendo 'filo 0 impostazione threadInt a 88'
  • filo 0 scrive la stringa di nuovo in staticVolatileTestString

questo è come previsto finora, ma poi:

  • filo 1-4 leggono la stringa su staticVolatileTestString
  • filo 1 viene aggiungendo 'filo 1 impostazione threadInt a 97'
  • filo 2 è aggiungendo 'filo 2 impostazione threadInt a 11'
  • filo 2 sta scrivendo la corda in staticVolatileTestString
  • ... filati 1, 2, 3 sono la lettura e aggiungendo, e la scrittura
  • 012.
  • filo 4 è scrivere la stringa di nuovo in staticVolatileTestString
  • e così via ...

vedere cosa è successo qui? thread 4 legge la stringa 'thread 0 setting threadInt su 88', aggiunge il suo 'thread 4 ...' e la riscrive, sovrascrivendo tutto ciò che i thread 1, 2 e 3 avevano già scritto nella stringa.

+0

Quindi stai dicendo che i thread stanno leggendo il valore simultaneamente e quindi scrivendo un'operazione separata l'una dall'altra? In questo caso, avrei bisogno di una sorta di blocco sul campo (che è quello che pensavo fosse volatile, ma immagino che in realtà garantisca solo che non ci sia cache, giusto?) EDIT: la risposta di Lucero conferma questo – Xenoprimate

+0

@Motig: Il lucchetto è lì quando legge la stringa, o la scrive di nuovo, ma non in mezzo. Ricorda: le stringhe sono immutabili, pertanto viene creata una nuova istanza di stringa e il riferimento a tale oggetto viene quindi scritto di nuovo nella variabile. –

+2

@Motig: la parola chiave volatile funziona alla grande con i tipi di dati primitivi mutabili (bool, int, double, ecc.). Con le stringhe, tuttavia, si desidera impostare un blocco dedicato, leggere la stringa, modificarla e riscriverla, quindi rilasciare il blocco. –