2012-06-12 7 views
6

Czytałem przez post tips and tricks i myślałem, że wypróbuję niektóre rzeczy z C#, których nigdy wcześniej nie robiłem. Dlatego poniższy kod nie służy faktycznemu celowi, ale jest tylko "funkcją testową", aby zobaczyć, co się stanie.C# ThreadStatic + członkowie volatile nie działają zgodnie z oczekiwaniami

W każdym razie, mam dwie statyczne prywatne pola:

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

Jak widać, jestem testowania ThreadStaticAttribute i lotnej hasła.

W każdym razie, mam metody badawczej, która wygląda tak:

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; 
} 

Co bym spodziewać wrócił z tej funkcji jest wyjście tak:

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 

Jednak to, co Rozumiem:

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 

Druga "połowa" wyniku jest zgodna z oczekiwaniami (co, jak sądzę, oznacza w polu ThreadStatic działa tak, jak myślałem), ale wydaje się, że kilka początkowych wyników zostało "pominiętych" od pierwszej "połowy".

Dodatkowo wątki w pierwszej "połowie" są nieczynne, ale rozumiem, że wątek nie uruchamia się natychmiast po wywołaniu Start(); ale zamiast tego wewnętrzne elementy sterujące OS będą uruchamiać wątki według własnego uznania. EDIT: Nie oni nie, rzeczywiście, po prostu uważamy, że były, bo mój mózg nie zdobywa kolejne numery


Więc moje pytanie brzmi: Co się dzieje źle powodować mnie stracić kilka linii w pierwsza "połowa" produkcji? Na przykład, gdzie jest linia "wątku 3 ustawienie wątkuInt do 84"?

+0

Uruchomiłem twój kod kilka razy i zawsze otrzymuję oczekiwany wynik ... –

+0

Założę się, że gdzieś w dole, połączenie z + = na łańcuchu uzyskuje wartość przed ustawieniem poprzedniego wątku i nowy wątek nadpisuje go nową wartością (dlatego wątek 1 miał pominięty wynik). – Charleh

+0

@DannyChen Mam czterordzeniowy rdzeń i nie jest to jedyny kod w tej aplikacji; nie wiem, czy któryś z nich miałby sens. Mogę przesłać cały kod gdzieś (to tylko dwa pliki .cs), jeśli chcesz? – Xenoprimate

Odpowiedz

7

Wątki są wykonywane w tym samym czasie. Co koncepcyjnie dzieje to:

staticVolatileTestString += Environment.NewLine + "\tthread " + ((int) o) + " setting threadInt to " + newVal; 
  1. Wątek 1 odczytuje staticVolatileTestString
  2. Temat 2 czyta staticVolatileTestString
  3. Temat 3 odczytuje staticVolatileTestString
  4. gwintu 1 dołącza rzeczy i pisze staticVolatileTestString powrotem
  5. gwintu 2 dołącza rzeczy i pisze staticVolatileTestString z powrotem
  6. Wątek 3 dołącza elementy i zapisuje staticVolatileTestString powrotem

To powoduje utratę linii. Lotny nie pomaga tutaj; cały proces łączenia łańcucha nie jest atomowy.Trzeba użyć blokady wokół tych operacji:

private static object sync = new object(); 

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

Tak samo samo powiedziałem tylko lepiej wyjaśnione: D haha ​​+1 – Charleh

2

MSDN opisuje słowo kluczowe volatile robi here:

Lotna słów kluczowych wskazuje, że pole może być modyfikowana przez wielu wątków jednocześnie wykonujących. Pola oznaczone jako volatile nie podlegają optymalizacji kompilacji, które zakładają dostęp przez pojedynczy wątek. Zapewnia to, że najbardziej aktualna wartość to zawsze obecna w polu.

Oznacza to, że w przykładzie, co się dzieje, jest o to (może ulec zmianie od czasu do czasu, w zależności od harmonogramu):

  • wątek 0 czyta ciąg z staticVolatileTestString
  • wątek 0 jest dołączanie 'wątek 0 ustawienie threadInt do 88'
  • gwintów 0 pisze ciąg z powrotem do staticVolatileTestString

jest tak jak oczekiwano, ale wówczas:

  • nici 1-4 czyta ciąg z staticVolatileTestString
  • wątku 1 jest dołączenie „ustawienie wątku 1 do 97” threadInt
  • gwintu 2 jest dodanie 'wątek 2 ustawienie threadInt do 11'
  • gwintu 2 pisze ciąg z powrotem do staticVolatileTestString
  • ... nici 1, 2, 3 czytania i dołączanie i pisanie
  • nić 4 jest pisanie ciąg z powrotem do staticVolatileTestString
  • i tak dalej ...

zobaczyć, co tu się stało? Wątek 4 odczytuje ciąg 'thread 0 setting threadInt to 88', dodaje swój "thread 4 ..." i zapisuje go, nadpisując wszystko, co wątek 1, 2 i 3 zapisał już w ciągu znaków.

+0

Więc czy mówisz, że wątki czytają wartość jednocześnie, a następnie pisząc jako oddzielną operację na siebie nawzajem? Jeśli tak jest, potrzebowałbym jakiejś blokady na polu (co uważałem za niestabilne, ale myślę, że to faktycznie zapewnia, że ​​nie ma buforowania, prawda?) EDYCJA: Odpowiedź Lucero potwierdza ten – Xenoprimate

+0

@Motig: Blokada jest obecna podczas czytania napisu lub zapisywania go, ale nie pomiędzy. Pamiętaj: ciągi są niezmienne, dlatego tworzona jest nowa instancja napisu, a odwołanie do niej jest następnie w formie wątku zapisywane z powrotem w zmiennej. –

+2

@Motig: słowo kluczowe volatile działa świetnie z zmiennymi typami danych pierwotnych (bool, int, double itd.). Jednak przy ciągach znaków należy ustawić dedykowaną blokadę, odczytać ciąg znaków, zmodyfikować go i zapisać, a następnie zwolnić blokadę. –

Powiązane problemy