2011-12-20 18 views
12

Se ho questo:Multithreading e chiusure NET

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

E questo metodo può essere chiamato contemporaneamente da più thread, e un thread è bloccato a DoStuffThatMightTakeAWhile, e poi una seconda filettatura chiama DoSomething con un diverso arg, questo cambierà il valore di someVar per tutti i thread e, di conseguenza, DoSomething restituirà la seconda versione di someArg in entrambe le chiamate o sarà presente uno someVar per ogni thread?

Modifica Penso che il mio Action avrebbe dovuto essere un Func così modificato.

risposta

16

ci sono una serie di confusioni nel. le risposte qui, per lo più basate sulla non verità che le variabili locali sono allocate "nella pila del thread" è falso e irrilevante.

È falso perché la variabile locale può essere allocata su un pool temporaneo o sul pool di archiviazione a lungo termine; anche se è allocato su un pool temporaneo, non è necessario che sia la memoria di stack; potrebbe essere un registro. È irrilevante perché a chi importa di quale pool è allocato lo spazio di archiviazione?

Il fatto rilevante è che è allocata una variabile locale di primo livello per attivazione del metodo. Più in generale, una variabile locale in un blocco viene allocata una volta per ogni blocco inserito; una variabile locale dichiarata nel corpo di un ciclo, ad esempio, viene allocata ogni volta che il ciclo gira.

Quindi, prendiamo in considerazione la tua domanda:

Questo metodo può essere chiamato contemporaneamente da più thread.Se un thread è bloccato a DoStuffThatMightTakeAWhile e quindi un secondo thread chiama DoSomething con un argomento diverso, questo cambierà il valore di someVar per tutti i thread?

No. C'è un nuovo "someVar" per ogni attivazione di DoSomething.

esiste una qualcheVar per ogni thread?

Un "someVar" esisterà per ciascuna attivazione. Se un thread fa esattamente un'attivazione, allora ci sarà esattamente un qualche Var per thread; se un thread fa un milione di attivazioni, ce ne sarà un milione.

Detto questo, la risposta di Jon Hanna è anche corretto: se si effettua un delegato, che sia legge e scrive una variabile locale, e voi mano che delegato fuori per più thread, poi tutte le attivazioni del capitale delegato la stessa variabile locale. Non è stata creata per te alcuna sicurezza di thread magica; se vuoi farlo, sei responsabile della sicurezza del thread.

+0

Quindi, in poche parole, una dichiarazione come 'stringa myVar;' creerà __create__ un nuovo 'myVar', proprio come se stessi creando un oggetto con la parola chiave' new'? Questo vale anche per le chiamate ricorsive (se "DoSomething" chiamerebbe "DoSomething")? – Juan

+2

@jsoldi: Il modo in cui l'allocatore di memoria crea nuovi locali e il modo in cui crea nuovo spazio per gli oggetti può essere molto diverso dietro le quinte, ma concettualmente sono la stessa cosa. E sì, le chiamate ricorsive sono * attivazioni * proprio come le chiamate non ricorsive. Ogni * attivazione * ottiene un nuovo lotto di locali. –

+1

Perché si chiama "attivazione del metodo" e non "chiamata di metodo". È un termine speciale? – Restuta

7

Variabili locali memorizzate nello stack del thread corrente, quindi ogni thread avrà il proprio stack e ciascuna propria variabile someVar al suo interno.

Poiché sarebbe valori diversi in ogni filetto

new Action(() => someVar)); 

catturerà il proprio valore di someVar.

Modifica

Ero semplicemente sbagliato dire che, come ha sottolineato Eric. Vedi la sua risposta per una spiegazione corretta.

+1

Dire "le variabili locali sono memorizzate nello stack del thread corrente" non è corretto. Qualsiasi variabile catturata in una chiusura viene allocata nell'heap. –

+0

Jason è corretto. Questa risposta non ha una logica valida. –

+0

Sì, mio ​​male, non modifico la mia risposta, perché il tuo è perfettamente perfetto. – Restuta

1

Come detto, ogni thread che colpisce DoSomething crea uno someVar separato nello stack e pertanto nessun thread ha alcun effetto su un altro someVar.

Vale la pena notare, tuttavia, che se un locale viene catturato in una chiusura e vi è un'introduzione multi-threading in tale ambito, ciò può effettivamente causare che thread diversi influenzino i valori reciproci, anche nel caso di i tipi di valore (che noi normalmente pensiamo come non qualcosa che un altro metodo può influenzare - in questo modo le chiusure non sono come metodi di classe:

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

dimostra

+0

Se ho capito bene, nel tuo esempio c'è solo un 'x' (quello creato nel thread principale) a cui si accede da ogni thread? – Juan

+0

Esattamente, il thread creato manualmente continua a scriverlo sulla console, mentre il thread principale lo incrementa. –

+1

Si noti che someVar non è allocato nello stack in primo luogo. I locali chiusi vengono allocati nell'heap perché le loro vite sono estese. –