2009-06-03 18 views
8

Tutti sanno che questo non è thread-safe:È il C# '??' thread operatore sicuro?

public StringBuilder Builder 
{ 
    get 
    { 
     if (_builder != null) 
      _builder = new StringBuilder(); 
     return _builder; 
    } 
} 

Che dire di questo?

public StringBuilder Builder 
{ 
    get { return _builder ?? (_builder = new StringBuilder()); } 
} 
+13

La specifica C# indica con attenzione quali operazioni sono atomiche; gli operatori a coalescenza nulla non sono atomici. L'operatore null coalescente è solo uno zucchero sintattico per il tuo primo pezzo di codice. Ma tu hai problemi più grandi qui; a chi importa se il campo è sicuro? Il costruttore non è protetto da bug! –

+4

Per le domande future in questo senso, sarebbe di grande aiuto se fornissi una definizione formulata con cura esattamente di cosa significa "thread safe" per te. La sicurezza del filo non è assoluta; piuttosto, il codice è thread-safe se il contratto d'uso implementato dai chiamanti è compatibile con quello atteso dal destinatario. Senza sapere quale contratto ti aspetti, è impossibile dire se il codice lo segue o meno. –

risposta

10

BEGIN EDIT

Sulla base della sua titolo modificato, l'operatore null coalescenza stessa sembra essere thread-safe (vedi Phil Haack's analysis). Sembra, tuttavia, che non garantisce contro le potenziali chiamate multiple al costruttore StringBuilder.

FINE EDIT

Hai un problema più grande con filettatura, e cioè che la proprietà del costruttore stesso rappresenta stato che può essere condiviso tra i thread. Anche se si rende sicuro il thread di inizializzazione pigro, non è possibile garantire che i metodi che utilizzano Builder lo facciano in modalità thread-safe.

// below code makes the getter thread safe 
private object builderConstructionSynch = new object(); 
public StringBuilder Builder 
{ 
    get 
    { 
     lock (builderConstructionSynch) 
     { 
      if (_builder == null) _builder = new StringBuilder(); 
     } 
     return _builder; 
    } 
} 

È possibile che questo impedirà il problema filettatura nella inizializzazione differita di _builder, ma a meno che non si sincronizzano le chiamate ai metodi di StringBuilder esempio, non la garanzia di sicurezza filo in qualsiasi metodo che consumano la proprietà Builder. Questo perché i metodi di istanza in StringBuilder non sono stati progettati per essere thread-safe. Vedere il testo seguente da MSDN StringBuilder page.

statici pubblici (Shared in Visual Basic) di questo tipo sono thread sicuri. Qualsiasi membro di istanza non è garantito come thread-safe.

Se stai consumando StringBuilder in più thread, è probabile che tu lo stia meglio incapsulando nella tua classe. Fare Builder privato ed esporre ciò che il comportamento è necessario come un metodo pubblico:

public void AppendString(string toAppend) 
{ 
    lock (Builder) 
    { 
     Builder.Append(toAppend); 
    } 
} 

In questo modo non sta scrivendo codice di sincronizzazione in tutto il luogo.

+0

L'ho solo pensato ?? è un'operazione atomica. ? non è thread-safe troppo? –

+3

Non posso dire se l'operatore a coalescenza nulla sia atomico, ma la mia asserzione è che si ha un problema più grande dal momento che StringBuilder non è intrinsecamente thread-safe. –

+1

Vedere la risposta modificata per la risposta sulla sicurezza del thread dell'operatore a coalescenza nulla (credito a Phil Haack). È thread-safe in quanto non crea condizioni di gara, ma potresti potenzialmente finire con due istanze separate di Builder se le condizioni sono perfette. –

8

NO per entrambe le versioni

10

che non più o meno è thread-safe; si potrebbero ancora avere due thread per il controllo nullo allo stesso tempo, quindi creare oggetti separati e non vedere l'altro.

+0

Qual è la tua opinione sull'uso di Interlocked.CompareExchange (ref _builder, new StringBuilder(), null)? – LBushkin

2

Le risposte sono corrette, entrambi non sono threadsafe. In realtà, sono per lo più equivalenti e l'operatore ?? è semplicemente un compilatore magico per rendere il codice più snello. È necessario utilizzare un meccanismo di sincronizzazione se si desidera che diventi a prova di codice.

2

Non ho provato io stesso approccio, ma se si vuole la sicurezza dei thread senza il sovraccarico di un sistema di bloccaggio e non siete preoccupati per creando potenzialmente e scartando un oggetto esempio, si potrebbe provare questo:

using System.Threading; 

public StringBuilder Builder 
{ 
    get 
    { 
     if (_builder != null) 
      Interlocked.CompareExchange(ref _builder, new StringBuilder(), null); 
     return _builder; 
    } 
} 

La chiamata a CompareExchange() eseguirà una sostituzione atomica del valore in _builder con una nuova istanza di StringBuilder solo se _builder == null.Tutti i metodi sulla classe Interlocked sono garantiti per NON essere preceduti da interruttori di thread.

+0

BTW, probabilmente è una cattiva idea condividere un'istanza di StringBuilder sui thread. SB non è intrinsecamente thread-safe, e non è chiaro, anche se lo fosse, che si possa fare qualcosa di significativo con esso attraverso i thread che non sono sincronizzati. – LBushkin