Strategie di implementazione per contatore atomico

3

In pratica ho una variabile contatore decrementata e frequentemente sottoposta a polling. Ci sono tre opzioni per implementarlo, che posso pensare:

Numero 1:

private volatile int count;
public void Finished() {
    Interlocked.Decrement(ref count);
}

public bool Done() {
    return count == 0;
}

Numero 2:

private int count;
public void Finished() {
    Interlocked.Decrement(ref count);
}

public bool Done() {
    return Volatile.Read(ref count) == 0;
}

Numero 3:

struct AtomicInteger {
    private int value;

    public int Get() {
        return Volatile.Read(ref value);
    }
    // other methods
}
// In actual class:
private AtomicInteger count;
public bool Done() {
     return count.Get() == 0;
}

Il primo fornisce un avvertimento del compilatore a causa del passaggio di una variabile volatile per riferimento. D'altra parte è chiaro dalla variabile definizione che la variabile è accessibile contemporaneamente che è chiaramente un segnale di avvertimento per chiunque stia guardando il codice. Se mai dovessi decidere di avere effettivamente bisogno di molto, questo metodo non funziona più.

Il secondo non dà alcun avvertimento, ma solo leggendo private int count non è chiaro che la variabile sia usata contemporaneamente, quindi questo deve essere esplicitamente indicato con un commento (in realtà avevo un bug in cui ho usato un lettura normale in un'asserzione anziché Volatile.Read - una cosa così semplice da trascurare.) Cambiare la variabile su un valore long invece non introduce alcun lavoro aggiuntivo.

Mi piace il numero 3 e non dovrebbe comportare un sovraccarico delle prestazioni rispetto alle altre soluzioni dopo l'inline. Lato negativo: duplicazione del codice per ogni primitivo. D'altra parte il codice è abbastanza semplice e può essere copiato senza timore di problemi ed è improbabile che cambi mai.

Qualche commento sui vantaggi / svantaggi di ciascun metodo che ho trascurato o che cosa sarebbe considerato idiomatico in C #?

    
posta Voo 30.07.2014 - 13:08
fonte

3 risposte

2

Dato che la sicurezza dei thread è un problema molto complesso, non posso commentare la correttezza di questo codice. Se riscontri un errore, fammelo sapere e modificherà o eliminerà la mia risposta.

Come panoramica dello scopo dei vari modificatori di semantica della memoria in C #, vedi:

  • SO: Volatile vs interblocco vs blocco
  • Nota: non accettare la risposta accettata al valore nominale. Leggi i link di riferimento (in particolare dagli autori delle specifiche e del framework C #) e decidi tu stesso.

Consigliato anche:

Inoltre, la classe Volatile di Joe Duffy, che è parte di .NET 4.5

Inoltre, nota la differenza tra "dovrebbe essere" vs. "il modo in cui è / sarà implementato". Pragmaticamente, solo l'ultimo è importante.

Sulla base del feedback di @Euphoric, sembra che il numero 2 di OP sia effettivamente la soluzione che soddisferà i requisiti dell'OP.

Il wrapping del codice in una classe (ad es. Numero 3 ) è una questione di stile di codifica e di refactoring.

    
risposta data 30.07.2014 - 14:57
fonte
0

Nessuno di questi è in realtà atomico. L'approccio idiomatico sarebbe qualcosa come

class Countdown {
  private int count;

  public Countdown(int ct) {
    count = ct; 
  }

 public bool Do(Action stuff) { // or whatever good name makes sense; possibly Func<T> returning T not bool
     var isDone = Interlocked.Decrement(ref count) == 0;
     if (!isDone) {
          // possibly try/catch
          stuff();
          return true;
     }

     return false;
  }
}
    
risposta data 30.07.2014 - 13:54
fonte
0

Direi che il numero uno è idiomatico in C #.

Personalmente preferisco il numero due: mi piace spostare le informazioni "volatile read" sulla lettura stessa piuttosto che interromperla nella dichiarazione dei campi. Inoltre il modificatore volatile in c # è una cosa strana e fuorviante che è spesso fraintesa, quindi penso che sia più facile per un principiante leggere e comprendere la documentazione di Volatile.Read. Infine è un'abitudine sana per i casi più complessi in cui è necessario enfatizzare le recinzioni implicite della memoria.

Per quanto riguarda il numero tre, è una questione di stile di codifica. Personalmente penso che sia superfluo a meno che non lo usi in molti posti.

Infine, per coloro che sono preoccupati per la correttezza, ecco una fonte (Joe Duffy) che afferma che il modificatore volatile non è necessario sui campi per semplici operazioni atomiche, il che significa che il n. 1 e il n. 2 sono equivalenti e corretti, e quindi è il n. 3. Ciononostante sostituirò "== 0" di "< = 0" nel caso in cui qualcosa dovesse andare storto in due anni.

    
risposta data 29.09.2014 - 08:31
fonte

Leggi altre domande sui tag