2013-04-22 14 views
5

Immaginate questo codice C# in qualche modo:Un altro thread può vedere una raccolta parzialmente creata quando si utilizza l'inizializzatore della raccolta?

SomeClass.SomeGlobalStaticDictionary = new Dictionary<int, string>() 
{ 
    {0, "value"}, 
}; 

Diciamo che nessuno sta usando qualsiasi barriera di memoria esplicita o di blocco per accedere al dizionario.

Se non viene eseguita alcuna ottimizzazione, il dizionario globale deve essere nullo (valore iniziale) o un dizionario correttamente costruito con una voce.

La domanda è: (? O qualsiasi altro dizionario parzialmente costruita non valido) Può l'effetto della Chiama, assegnando a SomeGlobalStaticDictionary essere riordinati in modo che qualche altro thread vedrebbe un SomeGlobalStaticDictionary non nullo vuoto

fa il cambiare la risposta se SomeGlobalStaticDictionary è volatile?

Dopo aver letto http://msdn.microsoft.com/en-us/magazine/jj863136.aspx (e anche la sua seconda parte) ho appreso che in teoria solo perché una variabile è assegnata nel codice sorgente altri thread potrebbero vederla in modo diverso a causa di molte ragioni. Ho guardato il codice IL, ma la domanda è se il compilatore JIT e/o la CPU non sono autorizzati a "svuotare" l'effetto della chiamata Aggiungi ad altri thread prima dell'assegnazione del SomGlobalStaticDictionary.

+0

Sospetto che la risposta ad entrambe le domande sia sì in presenza di più thread, ma non capisco abbastanza bene il modello di memoria per essere sicuro. – SLaks

+0

Valuta la possibilità di leggere http://www.amazon.com/CLR-via-Microsoft-Developer-Reference/dp/0735667454 – SLaks

+1

Questo codice non è legale; sei sicuro di volere 'var' lì dentro? I locali non sono volatili e non hanno mai punti nel loro nome; hai intenzione di essere un campo? –

risposta

4

Vorrei iniziare dicendo che io non conosco la risposta alla tua domanda, ma posso aiutare a semplificare il basso alla sua essenza:

unsafe class C 
{ 
    static int x; // Assumed to be initialized to zero 
    static int *p; // Assumed to be initialized to null 
    static void M() 
    { 
     int* t = &C.x; 
     *t = 1; 
     C.p = t; 
    } 
    ... 

Qui int è in piedi in per il dizionario, p è in piedi per il tuo campo che fa riferimento a un dizionario, t è il temporaneo creato e l'aggiunta di un elemento al dizionario viene modellata come mutando il valore del campo x. Quindi la sequenza di eventi qui è: ottenere memoria per il dizionario e salvarlo temporaneamente, quindi modificare la cosa a cui si fa riferimento e pubblicare il risultato.

La questione è se sotto il modello di memoria C#, un osservatore su un altro thread è permesso vedere che C.p sta indicando x e che x è ancora pari a zero.

Come ho detto, non so per certo la risposta a questo; Sarei interessato a scoprirlo.

Tuttavia, in cima alla mia testa: perché questo non è possibile? p e x possono trovarsi su pagine di memoria completamente diverse. Supponiamo che su alcuni processori il valore di x sia stato pre-scaricato ma non lo sia p. Potrebbe il processore osservare che p non è nullo ma x è ancora zero? Cosa lo ferma?

+0

Grazie. Sì, questa è l'essenza della domanda. Tuttavia, mi piacerebbe la risposta specifica per gli inizializzatori della raccolta, vale a dire che due di loro potrebbero avere risposte diverse se la specifica C# o .NET dice che una barriera di memoria dovrebbe essere generata solo per gli inizializzatori di raccolta (non riesco a trovare una cosa del genere al momento Presumo che non lo sia). Quindi, sta esaminando il codice IL generato abbastanza (tramite ad esempio ildasm) o il compilatore JIT può applicare regole diverse "solo per inizializzatori di raccolta". – Palo

+0

@Palo: Non lo fanno - il JIT non sa nulla degli inizializzatori della raccolta e C# spce non garantisce garanzie specifiche per l'inizializzatore della raccolta. –

+0

@Palo: l'unica garanzia che la specifica C# rende sulla visibilità delle cose che attraversano i thread riguarda effetti collaterali speciali come la scrittura di campi volatili o l'estrazione di blocchi o l'avvio di thread. Gli inizializzatori della raccolta non entrano in esso. –

6

In variabili locali, con ottimizzazione abilitata, il compilatore si (almeno a volte) compilare in codice che prima assegna alla variabile, quindi chiama Add (o imposta proprietà, per inizializzatori oggetto).

Se si utilizza un statico o una variabile di istanza, vedrete un comportamento diverso:

class Test 
{ 
    static List<int> StaticList = new List<int> { 1 }; 
    List<int> InstanceList = new List<int> { 2 }; 
} 

ha pronunciato la seguente L'inizializzatore di tipo:

.method private hidebysig specialname rtspecialname static 
     void .cctor() cil managed 
{ 
    // Code size  21 (0x15) 
    .maxstack 2 
    .locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0) 
    IL_0000: newobj  instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() 
    IL_0005: stloc.0 
    IL_0006: ldloc.0 
    IL_0007: ldc.i4.1 
    IL_0008: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) 
    IL_000d: nop 
    IL_000e: ldloc.0 
    IL_000f: stsfld  class [mscorlib]System.Collections.Generic.List`1<int32> Test::StaticList 
    IL_0014: ret 
} // end of method Test::.cctor 

E la seguente IL costruttore:

.method public hidebysig specialname rtspecialname 
     instance void .ctor() cil managed 
{ 
    // Code size  29 (0x1d) 
    .maxstack 3 
    .locals init (class [mscorlib]System.Collections.Generic.List`1<int32> V_0) 
    IL_0000: ldarg.0 
    IL_0001: newobj  instance void class [mscorlib]System.Collections.Generic.List`1<int32>::.ctor() 
    IL_0006: stloc.0 
    IL_0007: ldloc.0 
    IL_0008: ldc.i4.2 
    IL_0009: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0) 
    IL_000e: nop 
    IL_000f: ldloc.0 
    IL_0010: stfld  class [mscorlib]System.Collections.Generic.List`1<int32> Test::InstanceList 
    IL_0015: ldarg.0 
    IL_0016: call  instance void [mscorlib]System.Object::.ctor() 
    IL_001b: nop 
    IL_001c: ret 
} // end of method Test::.ctor 

In entrambi i casi, la raccolta viene compilata prima il campo è impostato. Questo non vuol dire che potrebbero non esserci ancora problemi con il modello di memoria, ma è non uguale al campo impostato per fare riferimento a una raccolta vuota e quindi alla chiamata Add effettuata. Dal punto di vista del thread di assegnazione, l'assegnazione avviene dopo lo Add.

In generale, entrambe le espressioni di inizializzazione degli oggetti e la raccolta di inizializzazione sono equivalenti a costruire l'oggetto utilizzando una variabile temporanea - così nel caso in cui lo si utilizza in un incarico, il setter di proprietà sono tutti chiamati prima l'assegnazione avviene .

Tuttavia, non credo che qualsiasi garanzie speciali siano fornite in base alla visibilità di altri thread per inizializzatori di oggetti/raccolte. Ti suggerirei di immaginare come sarebbe il codice se scritto "long-hand" secondo le specifiche, e quindi la ragione da lì.

Ci sono garanzie prestate per inizializzatori statici e costruttori - ma in primo luogo all'interno l'implementazione Microsoft di .NET piuttosto che garanzie "generali" (per esempioall'interno della specifica C# o delle specifiche ECMA).

+0

'SomeClass.SomeGlobalStaticDictionary'; sta cercando di chiedere un campo statico. E, come ho capito, sta chiedendo specificatamente del riordino della memoria. – SLaks

+0

@SLaks: Quindi la parte 'var' è fuorviante per iniziare. Sospetto che l'OP sia un po 'confuso in generale, ma chiarirò. –

+0

Potrebbe essere, ma penso che stava solo scrivendo velocemente e non ha pensato a 'var'. Sembra come se capisse quali sono i modelli di memoria. – SLaks

Problemi correlati