Domanda

Sto cercando di prevenire le gare di dati in un server multihread. Il mio problema è il seguente: c'è un List<RServer>, il tipo RServer è una classe con diversi campi. Ora, il server ha diversi thread in esecuzione contemporaneamente e possono modificare sia List (aggiungendo più elementi) sia le singole readonly object RServerLock = new object( ) istanze (cambiando i campi).

Quindi la mia strategia è quella di creare un readonly object RServerListLock = new object( ) in ciascuna delle lock istanze e inoltre un RServerLock e racchiudere tutto il codice che modifica (l'istanza <=> o <=>) in un < =>. È sicuro? Cosa succede se un thread tenta di bloccare un <=> mentre un altro lo sta bloccando?

È stato utile?

Soluzione

Se si dispone di un blocco conteso, il secondo thread deve attendere fino a quando il primo rilascia il blocco.

Il tuo piano suona quasi ok, ma devi bloccare anche quando leggi , per assicurarti di ottenere i valori più recenti e coerenti. Altrimenti potresti essere a metà della scrittura di alcuni valori in un thread e vedere alcuni dei nuovi valori - ma forse non tutti - e i vecchi valori, tutti allo stesso tempo in un thread diverso.

Se puoi evitare di farlo il più possibile, la tua vita sarà più semplice :) I tipi immutabili rendono il thread molto più semplice.

Non dimenticare che se hai mai un codice che avrà bisogno di due blocchi contemporaneamente (ad esempio aggiungendo un RServer e modificandone un altro atomicamente) devi assicurarti di acquisire sempre i blocchi lo stesso ordine - se un thread tenta di acquisire il blocco B mentre tiene il blocco A e un thread diverso tenta di acquisire il blocco A mentre tiene il blocco B, finirai con un deadlock.

Guarda il mio tutorial di threading o Joe Albahari per maggiori dettagli. Inoltre, se sei interessato alla concorrenza, Joe Duffy ha un libro eccellente che uscirà molto presto.

Altri suggerimenti

Sembra che tu abbia un candidato principale per un ReaderWriterLock. La migliore classe da utilizzare (se il tuo runtime lo supporta, credo 3.0+) è ReaderWriterLockSlim poiché ReaderWriterLock originale presenta problemi di prestazioni.

Uno degli autori della rivista MSDN ha anche riscontrato un problema con la classe RWLS, non entrerò nei dettagli qui, ma puoi guardarlo qui .

So che il seguente codice genererà la furia dei puristi IDisposable, ma a volte produce davvero un buon zucchero sintattico. In ogni caso, potresti trovare utile quanto segue:

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// specifying whether or not it may be upgraded.
    /// </summary>
    /// <param name="slim"></param>
    /// <param name="upgradeable"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim, bool upgradeable)
    {
        return new ReaderWriterLockSlimController(slim, true, upgradeable);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in read mode,
    /// and does not allow upgrading.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Read(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, true, false);
    } // IDisposable Read

    /// <summary>
    /// Opens the specified reader writer lock in write mode.
    /// </summary>
    /// <param name="slim"></param>
    /// <returns></returns>
    public static IDisposable Write(this ReaderWriterLockSlim slim)
    {
        return new ReaderWriterLockSlimController(slim, false, false);
    } // IDisposable Write

    private class ReaderWriterLockSlimController : IDisposable
    {
        #region Fields

        private bool _closed = false;
        private bool _read = false;
        private ReaderWriterLockSlim _slim;
        private bool _upgrade = false;

        #endregion Fields

        #region Constructors

        public ReaderWriterLockSlimController(ReaderWriterLockSlim slim, bool read, bool upgrade)
        {
            _slim = slim;
            _read = read;
            _upgrade = upgrade;

            if (_read)
            {
                if (upgrade)
                {
                    _slim.EnterUpgradeableReadLock();
                }
                else
                {
                    _slim.EnterReadLock();
                }
            }
            else
            {
                _slim.EnterWriteLock();
            }
        } //  ReaderWriterLockSlimController

        ~ReaderWriterLockSlimController()
        {
            Dispose();
        } //  ~ReaderWriterLockSlimController

        #endregion Constructors

        #region Methods

        public void Dispose()
        {
            if (_closed)
                return;
            _closed = true;

            if (_read)
            {
                if (_upgrade)
                {
                    _slim.ExitUpgradeableReadLock();
                }
                else
                {
                    _slim.ExitReadLock();
                }
            }
            else
            {
                _slim.ExitWriteLock();
            }

            GC.SuppressFinalize(this);
        } // void Dispose

        #endregion Methods
    } // Class ReaderWriterLockSlimController

Inseriscilo in una classe del metodo di estensione (classe statica pubblica [Nome]) e usalo come segue:

using(myReaderWriterLockSlim.Read())
{
  // Do read operations.
}

o

using(myReaderWriterLockSlim.Read(true))
{
  // Read a flag.
  if(flag)
  {
    using(myReaderWriterLockSlim.Write()) // Because we said Read(true).
    {
      // Do read/write operations.
    }
  }
}

o

using(myReaderWriterLockSlim.Write()) // This means you can also safely read.
{
  // Do read/write operations.
}

Se un thread tenta di bloccare un oggetto che è già bloccato, si bloccherà fino al rilascio del blocco. Non ci sono problemi di concorrenza quando due thread tentano di bloccarlo mentre il blocco è un'operazione atomica e uno dei thread sarà quindi sempre vittima del blocco e finirà per bloccarsi.

La tua strategia suona bene, a condizione che tu abbia bisogno di un blocco completo per l'istanza di RServer. Se è possibile bloccare specificamente i campi specifici dell'istanza RServer, ciò potrebbe essere più efficiente. Aumenterà tuttavia il numero di operazioni di blocco e sarà più complicato.

Questo è sicuro. Se un thread ha acquisito il blocco, gli altri thread dovranno attendere fino al rilascio del blocco.

Tuttavia, per quanto improbabile, potresti riscontrare un problema di prestazioni poiché il blocco potrebbe essere TROPPO globale. Dipende davvero da quale sia il tuo stato e da come è mutato da quei thread, quindi non posso aiutarti con quello.

Per riformulare un po ': ogni istanza di RServer ha una variabile chiamata RServerLock che verrà bloccata usando un blocco di blocco.

Il thread 1 (T1) prende un blocco su RServer 1 (R1). Il thread 2 (T2) arriva nel tentativo di modificare R1 che provoca l'inserimento del blocco di blocco R1. In questo caso T2 attenderà il completamento di T1.

Una cosa di cui fare molta attenzione è il numero di istanze RServer con cui si finisce. Se finisci con una pletora di istanze, stai trasportando circa 20 byte extra di dati solo per il blocco. A questo punto, potresti prendere in considerazione la possibilità di bloccare lo striping.

Se capisco correttamente la tua domanda, sembra che tu stia cercando di creare una ruota in cui già esistono ruote perfettamente buone.

Controlla lo spazio dei nomi system.threading in MSDN. Ha molti meccanismi di blocco progettati specificamente per affrontare situazioni come questa.

http://msdn.microsoft.com/en-us /library/system.threading.aspx

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top