Pergunta

Estou tentando evitar disputas de dados em um servidor multihread.Meu problema é o seguinte:existe um List<RServer>, o tipo RServer é uma classe com vários campos.Agora, o servidor tem vários threads em execução ao mesmo tempo e eles podem modificar tanto o List (adicionando mais itens) e o indivíduo RServer instâncias (alterando os campos).

Então minha estratégia é fazer um readonly object RServerLock = new object( ) em cada um dos RServer instâncias e adicionalmente um readonly object RServerListLock = new object( ) e inclua todo o código que modifica (o List ou um RServer exemplo) em um lock.Isso é seguro?O que acontece se um thread tentar bloquear um RServerLock enquanto outro está trancando?

Foi útil?

Solução

Se você tiver um bloqueio contestado, o segundo thread terá que esperar até que o primeiro libere o bloqueio.

Seu plano parece aproximadamente ok - mas você precisa bloquear quando leitura dados também, para garantir que você obtenha os valores mais recentes e consistentes.Caso contrário, você poderia estar escrevendo alguns valores em um thread e ver alguns dos novos valores - mas possivelmente não todos - e os valores antigos, todos ao mesmo tempo em um thread diferente.

Se você evitar fazer isso o máximo possível, sua vida será mais fácil :) Tipos imutáveis ​​​​tornam o threading muito mais simples.

Não se esqueça que se você tiver um código que precise de dois bloqueios ao mesmo tempo (por exemplo,adicionando um RServer e modificando outro, atomicamente) você deve certifique-se de sempre adquirir bloqueios na mesma ordem - se um thread tentar adquirir o bloqueio B enquanto estiver segurando o bloqueio A, e um thread diferente tentar adquirir o bloqueio A enquanto estiver segurando o bloqueio B, você terminará em um impasse.

Veja meu tutorial de rosqueamento ou Joe Albahari para mais detalhes.Além disso, se você estiver interessado em simultaneidade, Joe Duffy tem um excelente livro que será lançado muito em breve.

Outras dicas

Parece que você tem um candidato principal para um leiteiro. A melhor classe a ser usada (se o seu tempo de execução suportar, acho que o 3.0+) é o ReaderWriterLockslim, pois o ReaderWriterLock original tem problemas de desempenho.

Um dos autores da revista MSDN também se deparou com um problema com a classe RWLS, não vou entrar nos detalhes aqui, mas você pode olhar para isso aqui.

Sei que o código a seguir gerará a fúria dos puristas idisposíveis, mas às vezes realmente faz um bom açúcar sintático. De qualquer forma, você pode achar o seguinte útil:

    /// <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

Coloque isso em uma classe de método de extensão (classe estática pública [nome]) e use -a da seguinte forma:

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

Ou

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

Ou

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

Se um thread tentar bloquear um objec que já está bloqueado, ele bloqueará até que o bloqueio seja liberado. Não há problemas de simultaneidade quando dois threads tentam travá -lo ao mesmo tempo em que a trava é uma operação atômica e um dos threads sempre será vítima da fechadura e acabará bloqueando.

Sua estratégia parece parecer, desde que você precise de um bloqueio completo para a instância do RSERVER. Se você pode bloquear os campos específicos da instância do RServer especificamente, isso pode ser mais eficiente. No entanto, aumentará o número de operações de bloqueio e será mais complicado.

Isso é seguro. Se um thread tiver adquirido o bloqueio, outros threads terão que esperar até que a fechadura seja liberada.

No entanto, por mais improvável que seja, você pode atingir um problema de desempenho, pois o bloqueio pode ser muito global. Realmente depende do que é seu estado e de como é mutado com esses tópicos, por isso não posso ajudá -lo com isso.

Para reafirmar um pouco - cada instância do RSERVER possui uma variável chamada RServerlock, que será bloqueada usando um bloco de bloqueio.

O thread 1 (T1) pega um bloqueio no RServer 1 (R1). O thread 2 (T2) vem em uma tentativa de modificar R1, o que faz com que o bloco de bloqueio R1 seja inserido. Nesse caso, o T2 esperará até que o T1 termine.

Uma coisa a ser realmente cuidadosa é quantas instâncias do RSERVER você acaba. Se você acabar com uma infinidade de instâncias, estará carregando 20 bytes extras de dados apenas para bloqueio. Neste ponto, você pode considerar a faixa de bloqueio.

Se eu entendo sua pergunta corretamente, parece que você está tentando criar uma roda onde já existem rodas perfeitamente boas.

Confira o espaço de nome do sistema.Threading no MSDN. Possui muitos mecanismos de bloqueio projetados especificamente para lidar com situações como essa.

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top