2011-12-20 12 views
12

Jeśli mam to:Wielowątkowość i zamknięć w .NET

public string DoSomething(string arg) 
{ 
    string someVar = arg; 
    DoStuffThatMightTakeAWhile(); 
    return SomeControl.Invoke(new Func<string>(() => someVar)); 
} 

A metoda ta może być wywołana z wielu wątków jednocześnie, a jeden wątek jest zatrzymany na DoStuffThatMightTakeAWhile, a następnie wywołuje drugi wątek DoSomething z innym arg, czy to zmieni wartość someVar dla wszystkich wątków i w związku z tym, DoSomething zwróci drugą wersję someArg dla obu połączeń, czy będzie istnieć jedna someVar dla każdego wątku?

Edit myślę, że moja Action powinno być Func tak edytowane go.

Odpowiedz

16

Istnieje wiele niejasności w. odpowiedzi tutaj, w większości oparte na nieprawdzie, że zmienne lokalne są przydzielane "na stosie wątku" .To jest bo fałszywy i nieistotny.

Jest to wartość false, ponieważ zmienna lokalna może być przydzielona do pewnej puli tymczasowej lub do puli długoterminowej; nawet jeśli jest przydzielony do puli tymczasowej, nie musi to być pamięć stosu; może to być rejestr. Nie ma to znaczenia, ponieważ kogo obchodzi, na jaką pulę jest przeznaczona pamięć?

Istotny jest fakt, że jest przypisywana lokalna zmienna najwyższego poziomu dla każdej aktywacji metody. Bardziej ogólnie, zmienna lokalna w bloku jest przydzielana raz na wpisywany blok; Zmienna lokalna zadeklarowana w ciele pętli jest na przykład przydzielana za każdym razem, gdy pętla się kręci.

Zatem rozważmy zapytanie:

Metoda ta może być wywołana z wielu wątków jednocześnie.Jeśli jeden wątek utknął w DoStuffThatMightTakeAWhile, a drugi wątek wywoła DoSomething z innym arg, czy zmieni to wartość someVar dla wszystkich wątków?

nr Jest nowy "somevar" dla aktywacji każdego z doSomething.

Czy dla każdego wątku będzie istniała jakaś wartość?

One "somevar" będzie istnieć dla każdego aktywacji. Jeśli wątek wykonuje dokładnie jedną aktywację, wtedy będzie dokładnie jeden jedenVar na wątek; jeśli wątek wykonuje milion aktywacji, będzie ich milion.

Powiedział, że odpowiedź Jon Hanna jest również prawidłowa: jeśli popełnisz delegata, który zarówno odczytuje i zapisuje do zmiennej lokalnej, a ręka że delegata się wiele wątków, a następnie wszystkie aktywacje udziału delegat ta sama zmienna lokalna. Nie stworzono dla ciebie magicznego bezpieczeństwa nici; jeśli chcesz to zrobić, jesteś odpowiedzialny za zapewnienie bezpieczeństwa wątku.

+0

Tak więc w kilku słowach deklaracja typu 'string myVar;' będzie __create__ nowa 'myVar', podobnie jak ja stworzyłem obiekt ze słowem kluczowym' new'? Czy dotyczy to również wywołań rekursywnych (jeśli 'DoSomething' nazwałoby' DoSomething')? – Juan

+2

@jsoldi: Sposób, w jaki alokator pamięci tworzy nowych lokali i sposób, w jaki tworzy nowe obiekty pamięci, może być bardzo różny za kulisami, ale koncepcyjnie są one tym samym. I tak, rekursywne połączenia są * aktywacjami * tak jak połączenia nierekurencyjne. Każda * aktywacja * dostaje nową partię mieszkańców. –

+1

Dlaczego nazywasz to "aktywacją metody", a nie "wywołaniem metody". Czy to jest specjalne określenie? – Restuta

7

Zmienne lokalne przechowywane w stosie bieżącego wątku, więc każdy wątek będzie miał własny stos i każdą własną zmienną someVar.

Ponieważ byłoby to różne wartości w każdym wątku

new Action(() => someVar)); 

przechwyci własną wartość someVar.

Edit

byłem po prostu źle mówiąc, że jak Eric wskazał. Zobacz jego odpowiedź dla poprawnego wyjaśnienia.

+1

Mówienie "zmienne lokalne są przechowywane na stosie bieżącego wątku" jest nieprawidłowe. Każda zmienna wychwycona w zamknięciu jest przydzielana na stercie. –

+0

Jason jest poprawny. Ta odpowiedź nie zawiera logiki dźwiękowej. –

+0

Tak, moje złe, nie zmienię mojej odpowiedzi, bo twoja jest po prostu doskonale dobra. – Restuta

1

Jak już powiedziałem, każdy wątek trafiający na DoSomething tworzy na swoim stosie oddzielny someVar, a zatem żaden wątek nie ma żadnego wpływu na czyjeś inne. someVar. Warto zauważyć jednak, że jeśli lokalny jest przechwytywany w zamknięciu i istnieje wiele wątków inicjowanych w tym zakresie, to może to powodować, że różne wątki wpływają na wartości, jakie wzajemnie widzi, nawet w przypadku typy wartości (co zwykle uważamy za nie coś, co inna metoda może wpłynąć - w ten sposób zamknięcia nie są niczym metod klasa:

public static void Main(string[] args) 
{ 
    int x = 0; 
    new Thread(() => {while(x != 100){Console.WriteLine(x);}}).Start(); 
    for(int i = 0; i != 100; ++i) 
    { 
     x = i; 
     Thread.Sleep(10); 
    } 
    x = 100; 
    Console.ReadLine(); 
} 

Demonstruje to

+0

Jeśli dobrze rozumiem, w twoim przykładzie jest tylko jeden 'x' (ten utworzony w głównym wątku), do którego ma dostęp każdy wątek? – Juan

+0

Dokładnie, ręcznie utworzony wątek zapisuje go na konsoli, podczas gdy główny wątek zwiększa go. –

+1

Należy zauważyć, że someVar nie jest przydzielane na stosie w pierwszej kolejności. Zamknięte locals są przydzielane na stercie, ponieważ ich czas życia jest wydłużony. –