2010-06-04 5 views
12

Ho utilizzato questo modello per inizializzare i dati statici nelle mie classi. Mi sembra thread-safe, ma so quanto possano essere sottili problemi di threading. Ecco il codice:Inizializzazione di variabili statiche thread-safe

public class MyClass // bad code, do not use 
{ 
    static string _myResource = ""; 
    static volatile bool _init = false; 
    public MyClass() 
    { 
     if (_init == true) return; 
     lock (_myResource) 
     { 
      if (_init == true) return; 
      Thread.Sleep(3000); // some operation that takes a long time 
      _myResource = "Hello World"; 
      _init = true; 
     } 
    } 
    public string MyResource { get { return _myResource; } } 
} 

Ci sono dei buchi qui? Forse c'è un modo più semplice per farlo.

AGGIORNAMENTO: Il consenso sembra essere che un costruttore statico è la strada da percorrere. Ho trovato la seguente versione usando un costruttore statico.

public class MyClass 
{ 
    static MyClass() // a static constructor 
    { 
     Thread.Sleep(3000); // some operation that takes a long time 
     _myResource = "Hello World"; 
    } 

    static string _myResource = null; 

    public MyClass() { LocalString = "Act locally"; } // an instance constructor 

    // use but don't modify 
    public bool MyResourceReady { get { return _myResource != null; } } 
    public string LocalString { get; set; } 
} 

Spero che questo è meglio.

+0

@RaoulRobin, un costruttore statico sarebbe in ordine, ma solo se si desidera una classe statica. Se non si desidera condividere i campi in tutte le istanze di MyClass, è necessario utilizzare la sincronizzazione corretta. – Kiril

+0

Nella versione reale la risorsa statica è Dictionary <> che viene precaricata, anziché essere letta contemporaneamente in più istanze della classe. MSDN dice che le letture concorrenti sono OK finché il dizionario non viene modificato durante le letture. Grazie per il commento. – RaoulRubin

risposta

1

A seconda del caso d'uso (vale a dire se le discussioni non hanno bisogno di passare informazioni tra loro utilizzando questa variabile) , contrassegnando la variabile membro come [ThreadStatic] potrebbe essere una soluzione.
Vedere here.

6

Eseguire un lock() su _myResource e modificarlo all'interno dell'istruzione lock() sembra una cattiva idea. Considerate seguente flusso di lavoro:

  1. filo 1 chiamate MyClass().
  2. interruzioni di esecuzione prima della riga _init = true; subito dopo l'assegnazione di _myResource.
  3. Il processore passa al thread 2.
  4. thread 2 chiama MyClass(). Dal _init è ancora false e refrence _myResource modificato, con successo entra nel blocco di istruzioni lock().
  5. _init è ancora false, quindi il thread 2 riassegna _myResource.

Soluzione: creare una statica object e blocco su questo oggetto, invece di risorse inizializzato:

private static readonly object _resourceLock = new object(); 

/*...*/ 

lock(_resourceLock) 
{ 
    /*...*/ 
} 
+0

Buon punto. Non capisco davvero la sequenza di esecuzione che si svolge durante la costruzione. Vedo il problema Penso che ci possa essere anche un problema con l'assegnazione di un nuovo valore alla stringa. PER ESEMPIO. _myResource = "Some New Value"; – RaoulRubin

+0

Un'altra cosa da ricordare: le costanti di stringa sono internate per impostazione predefinita (ovvero, 'object.RefererecnceEquals (s1, s2)' restituirà true per le costanti di stringa uguale s1 e s2), quindi è possibile eseguire operazioni come lock ("MyString "), ma questo design non è raccomandato da MS. Ecco perché l'uso della variabile stringa come argomento nell'istruzione 'lock()' può portare a risultati inaspettati. – max

3

La classe non è sicuro:

  1. Si modifica l'oggetto che stai blocco su dopo averlo bloccato.
  2. Si dispone di una proprietà che ottiene la risorsa senza bloccarla.
  3. Si blocca su un primitive type, che in genere non è una buona pratica.

Questo dovrebbe farlo per voi:

public class MyClass 
{ 
    static readonly object _sync = new object(); 
    static string _myResource = ""; 
    static volatile bool _init = false; 

    public MyClass() 
    { 
     if (_init == true) return; 
     lock (_sync) 
     { 
      if (_init == true) return; 
      Thread.Sleep(3000); // some operation that takes a long time 
      _myResource = "Hello World"; 
      _init = true; 
     } 
    } 

    public string MyResource 
    { 
     get 
     { 
      MyClass ret; // Correct 
      lock(_sync) 
      { 
       ret = _myResource; 
      } 
      return ret; 
     } 
    } 
} 

Aggiornamento:
corretta, la risorsa statico non deve essere restituito direttamente ... Ho corretto il mio esempio di conseguenza.

+0

Sei un grande punto. Il blocco di _myResource era una cattiva idea. Penso che ci fosse anche un problema più grande: il getter stava restituendo un riferimento alla risorsa statica. Nel tuo esempio non penso che get {lock (_sync) risolva questo problema. Forse una soluzione migliore sarebbe stata bloccare, quindi fare una copia e restituirla. Grazie. – RaoulRubin

+0

La stringa non è un tipo primitivo, ma è comunque una cattiva idea bloccarla poiché se è internata, l'oggetto lock può essere esposto all'esterno della classe. –

+0

@Matt, in base a msdn una stringa è una primitiva: http://msdn.microsoft.com/en-us/library/ms228360%28VS.80%29.aspx – Kiril

0
static string _myResource = ""; 
... 
public MyClass() 
{ 
    ... 
    lock (_myResource) 
    { 
    } 
} 

A causa dell'internamento delle stringhe, non si deve bloccare un letterale di stringa.

UPDATE

Sì, @RaoulRubin, quindi se si blocca su una stringa letterale e che stringa letterale viene utilizzato da più classi, allora si può condividere quella serratura. Questo può potenzialmente causare un comportamento imprevisto.

+1

FYI Ho dovuto cercare internato. Ecco il MSDN: Il common language runtime conserva la memoria delle stringhe mantenendo una tabella, chiamata intern pool, che contiene un riferimento unico a ogni stringa letterale univoca dichiarata o creata a livello di codice nel programma. Di conseguenza, un'istanza di una stringa letterale con un valore particolare esiste solo una volta nel sistema. – RaoulRubin

+0

Per il downvoter, non mi dispiacerebbe un commento. La domanda è quali sono i buchi nel codice (dal punto di vista della sicurezza del thread). Ho pubblicato un foro che il poster stava eseguendo, bloccando le costanti di stringa. Forse hai pensato che il codice postato fosse una raccomandazione? –