Вопрос

Я пытаюсь предотвратить скачки данных на многопоточном сервере.Моя проблема заключается в следующем:существует List<RServer>, тип RServer это класс с несколькими полями.Теперь на сервере есть несколько потоков, запущенных одновременно, и они могут изменять как List (добавление дополнительных элементов) и отдельного RServer экземпляры (изменение полей).

Итак, моя стратегия состоит в том, чтобы сделать readonly object RServerLock = new object( ) в каждом из RServer экземпляры и, дополнительно, readonly object RServerListLock = new object( ) и вложите весь код, который изменяет либо ( List или RServer экземпляр) в lock.Это безопасно?Что произойдет, если поток попытается заблокировать RServerLock в то время как другой кто-то запирает его?

Это было полезно?

Решение

Если у вас есть конкурентная блокировка, второй поток должен ждать, пока первый не снимет блокировку.

Ваш план звучит почти , все в порядке - но вам также нужно заблокировать данные при чтении данных, чтобы убедиться, что вы получаете самые последние и согласованные значения. В противном случае вы могли бы наполовину записать некоторые значения в одном потоке и увидеть некоторые из новых значений (но, возможно, не все) и старые значения одновременно в другом потоке.

Если вы сможете избежать этого в максимально возможной степени, ваша жизнь станет проще :) Неизменяемые типы значительно упрощают работу с потоками.

Не забывайте, что если у вас когда-нибудь будет код, который будет нуждаться в двух блокировках одновременно (например, добавление одного RServer и изменение другого атомарно), вы должны убедиться, что вы всегда получаете блокировки в тот же порядок - если один поток пытается получить блокировку B, пока он удерживает блокировку A, а другой поток пытается получить блокировку A, пока он удерживает блокировку B, вы получите тупик.

См. мой учебник по работе с потоками или Джо Албахари для более подробной информации. Кроме того, если вас интересует параллелизм, у Джо Даффи есть отличная книга , в которой скоро выйдет.

Другие советы

Похоже, у вас есть отличный кандидат на блокировку ReaderWriterLock.Лучшим классом для использования (если ваша среда выполнения поддерживает его, я думаю, 3.0+) является ReaderWriterLockSlim, поскольку исходный ReaderWriterLock имеет проблемы с производительностью.

Один из авторов журнала MSDN также столкнулся с проблемой с классом RWLS, я не буду здесь вдаваться в подробности, но вы можете посмотреть на это здесь.

Я знаю, что следующий код вызовет ярость пуристов IDisposable, но иногда из него действительно получается приятный синтаксический сахар.В любом случае, вам может оказаться полезным следующее:

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

Поместите это в класс метода расширения (public static class [Name]) и используйте его следующим образом:

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

Или

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

Или

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

Если поток пытается заблокировать объект, который уже заблокирован, он будет блокироваться до тех пор, пока блокировка не будет снята. Нет проблем с параллелизмом, когда два потока пытаются заблокировать его одновременно, поскольку блокировка является атомарной операцией, и поэтому один из потоков всегда будет жертвой блокировки и в конечном итоге будет блокироваться.

Ваша стратегия звучит здраво при условии, что вам нужна полная блокировка для экземпляра RServer. Если вы можете заблокировать определенные поля экземпляра RServer, это может быть более эффективным. Однако это увеличит количество операций блокировки и будет более сложным.

Это безопасно. Если один поток получил блокировку, другим потокам придется ждать, пока блокировка не будет снята.

Однако, как бы это ни было невероятно, вы можете столкнуться с проблемой производительности, поскольку блокировка может быть слишком глобальной. Это действительно зависит от того, каково ваше состояние и как оно видоизменено этими нитями, поэтому я не могу вам с этим помочь.

Для краткого изложения - каждый экземпляр RServer имеет переменную с именем RServerLock, которая будет заблокирована с помощью блока блокировки.

Поток 1 (T1) блокирует RServer 1 (R1). Поток 2 (T2) пытается изменить R1, что приводит к вводу блока блокировки R1. В этом случае T2 будет ждать окончания T1.

Одна вещь, о которой нужно быть очень осторожным, это количество экземпляров RServer, которые вы получите Если вы получите множество экземпляров, вы переносите дополнительные 20 байтов данных только для блокировки. На этом этапе вы можете рассмотреть возможность чередования блокировок.

Если я правильно понимаю ваш вопрос, похоже, вы пытаетесь создать колесо, в котором уже есть совершенно хорошие колеса.

Проверьте пространство имен system.threading в MSDN. Он имеет множество механизмов блокировки, разработанных специально для таких ситуаций.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top