2010-02-11 8 views
7

Disclaimer: i miei post sono apparentemente sempre prolissi. Se ti capita di conoscere la risposta alla domanda del titolo, sentiti libero di rispondere semplicemente senza leggere la mia discussione estesa qui sotto.Perché interlocked.CompareExchange <T> supporta solo i tipi di riferimento?


La classe System.Threading.Interlocked fornisce alcuni metodi molto utili per aiutare nella scrittura di codice thread-safe. Uno dei metodi più complessi è CompareExchange, che può essere utilizzato per calcolare un totale parziale che può essere aggiornato da più thread.

Poiché l'uso di CompareExchange è un po 'complicato, ho pensato che un'idea piuttosto di buon senso per fornire alcuni metodi di supporto per esso:

// code mangled so as not to require horizontal scrolling 
// (on my monitor, anyway) 
public static double Aggregate 
(ref double value, Func<double, double> aggregator) { 
    double initial, aggregated; 

    do { 
     initial = value; 
     aggregated = aggregator(initial); 
    } while (
     initial != Interlocked.CompareExchange(ref value, aggregated, initial) 
    ); 

    return aggregated; 
} 

public static double Increase(ref double value, double amount) { 
    return Aggregate(ref value, delegate(double d) { return d + amount; }); 
} 

public static double Decrease(ref double value, double amount) { 
    return Aggregate(ref value, delegate(double d) { return d - amount; }); 
} 

Ora, forse io sono solo colpevole di essere generic-felice (Lo ammetto, questo è spesso vero); ma mi sembra stupido per me limitare la funzionalità fornita dai metodi sopra solo ai valori double (o, più precisamente, per me dover scrivere versioni sovraccaricate dei metodi precedenti per ogni tipo che voglio supportare). Perché non posso farlo?

// the code mangling continues... 
public static T Aggregate<T> 
(ref T value, Func<T, T> aggregator) where T : IEquatable<T> { 
    T initial, aggregated; 

    do { 
     initial = value; 
     aggregated = aggregator(initial); 
    } while (
     !initial.Equals(
      Interlocked.CompareExchange<T>(ref value, aggregated, initial) 
     ) 
    ); 
} 

non posso farlo perché Interlocked.CompareExchange<T> ha apparentemente un vincolo where T : class e non capisco perché. Voglio dire, forse perché ci sono già sovraccarichi per CompareExchange che accettano Int32, Int64, Double, ecc .; ma a stento sembra una buona logica. Nel mio caso, ad esempio, sarebbe molto utile poter usare il metodo Aggregate<T> per eseguire una vasta gamma di calcoli atomici.

risposta

11

Interlocked.CompareExchange è pensato per essere implementato con istruzioni atomiche native fornite direttamente dal processore. È inutile che qualcosa del genere usi internamente uno lock (è progettato per scenari senza blocco).

I processori che forniscono istruzioni di scambio di confronto atomico lo supportano naturalmente come piccole operazioni "di dimensioni di registro" (ad esempio la più grande istruzione di confronto su un processore Intel x64 è cmpxchg16b che funziona su valori a 128 bit).

Un tipo di valore arbitrario può essere potenzialmente più grande di quello e lo scambio di confronto potrebbe non essere possibile con una singola istruzione. Confrontare-scambiare un tipo di riferimento è facile. Indipendentemente dalla sua dimensione totale in memoria, verrà confrontato e copiato un piccolo puntatore di una dimensione nota. Questo vale anche per i tipi primitivi come Int32 e Double —, tutti sono piccoli.

+0

Penso che questo sia stato messo a fuoco per me una volta che ho letto questo nella documentazione MSDN poco dopo aver postato la domanda (per il sovraccarico che accetta i parametri di 'Object'):" Gli oggetti vengono confrontati per l'uguaglianza di riferimento, piuttosto che "Object.Equals". Di conseguenza, due istanze in scatola dello stesso tipo di valore (ad esempio, il numero intero 3) sembrano sempre non uguali e non viene eseguita alcuna operazione. Non utilizzare questo sovraccarico con tipi di valore. " Ovviamente - non userà il metodo 'Equals', ma piuttosto controllerà i riferimenti stessi. –

+0

Quindi, chiaramente, la mia unica scelta sarebbe in effetti scrivere tutti quei metodi sovraccaricati (per 'int',' long', ecc.) Dopotutto - se volessi davvero seguire questa strada. Destra? –

+0

@Dan: Non sono sicuro di come lo farebbe per un tipo di valore più grande * atomicamente * senza usare un 'lock' (scambiando un gruppo di valori interi). Il punto di confronto-scambio è che è atomico; cioè, non ci sarà un punto nel tempo in cui è possibile osservare una parte dell'operazione è fatta e l'altra parte non lo è. Mettere una serie di scambi di confronti l'uno sull'altro non aiuta a rendere l'operazione globale atomica (ogni parte è atomica, ma l'operazione nel suo insieme, non lo è). –

-1

Sospetto che lo Interlocked.CompareExchange<T> esegua appena un puntatore atomico-swap sotto il cofano.

Provare a farlo con un tipo di valore probabilmente non darebbe i risultati che ci si aspetta.

È possibile, naturalmente, digitare il valore di casella in un object prima di utilizzarli.

+1

Risulta il pugilato il tipo di valore come 'oggetto' non ** funzionerebbe **, in realtà. Vedi il mio primo commento alla risposta di Mehrdad. –

1

Poiché questo sovraccarico è specificamente destinato a confrontare e scambiare un riferimento. Non esegue un controllo di uguaglianza usando il metodo Equals().Dal momento che un tipo di valore non avrebbe mai un'uguaglianza di riferimento con il valore con cui lo stai confrontando, la mia ipotesi è che hanno limitato T alla classe per evitare l'uso improprio.

+0

Scommetto che il tuo cognome ti dà molto credito tra i tuoi colleghi. –

+0

Questo era un +1 da parte mia, a proposito; Non stavo prendendo in giro. –

+0

LOL va bene, non l'ho preso come tale. A proposito, molto buona domanda. – Josh