Это хороший дизайн для создания многопоточных классов в C #? - PullRequest
12 голосов
/ 25 января 2010

Часто, когда я хочу класс, который является потокобезопасным, я делаю что-то вроде следующего:

public class ThreadSafeClass
{
    private readonly object theLock = new object();

    private double propertyA;
    public double PropertyA
    {
        get
        {
            lock (theLock)
            {
                return propertyA;
            }
        }
        set
        {
            lock (theLock)
            {
                propertyA = value;
            }
        }
    }

    private double propertyB;
    public double PropertyB
    {
        get
        {
            lock (theLock)
            {
                return propertyB;
            }
        }
        set
        {
            lock (theLock)
            {
                propertyB = value;
            }
        }
    }

    public void SomeMethod()
    {
        lock (theLock)
        {
            PropertyA = 2.0 * PropertyB;
        }
    }
}

Это работает, но очень многословно. Иногда я даже создаю объект блокировки для каждого метода и свойства, создавая больше деталей и сложности.

Я знаю, что также возможно блокировать классы, используя атрибут Synchronization, но я не уверен, насколько хорошо это масштабируется - поскольку я часто ожидаю иметь сотни тысяч, если не миллионы, экземпляров поточно-безопасных объектов , Этот подход создаст контекст синхронизации для каждого экземпляра класса и требует, чтобы класс был производным от ContextBoundObject и, следовательно, не мог быть получен из чего-либо еще - поскольку C # не допускает множественного наследования - что является ограничителем показа во многих случаях.

Редактировать: Как подчеркивали некоторые респонденты, не существует "серебряной пули", ориентированной на многопотоковое исполнение. Я просто пытаюсь понять, является ли шаблон, который я использую, одним из хороших решений. Конечно, лучшее решение в любой конкретной ситуации зависит от проблемы. Некоторые из ответов ниже содержат альтернативные варианты, которые следует учитывать.

Редактировать: Более того, существует более одного определения безопасности потока. Например, в моей реализации выше, следующий код НЕ будет потокобезопасным:

var myObject = new ThreadSafeClass();
myObject.PropertyA++; // NOT thread-safe

Итак, приведенное выше определение класса представляет собой хороший подход? Если нет, что бы вы порекомендовали для дизайна с аналогичным поведением, которое было бы поточно-ориентированным для аналогичного набора применений?

Ответы [ 7 ]

8 голосов
/ 25 января 2010

Не существует единого решения проблемы многопоточности. Проведите некоторое исследование по созданию неизменяемых классов и узнайте о различных примитивах синхронизации.

Это пример полупостоянного или программируемого класса.

public class ThreadSafeClass
{
    public double A { get; private set; }
    public double B { get; private set; }
    public double C { get; private set; }

    public ThreadSafeClass(double a, double b, double c)
    {
        A = a;
        B = b;
        C = c;
    }

    public ThreadSafeClass RecalculateA()
    {
        return new ThreadSafeClass(2.0 * B, B, C);
    }
}

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

public class ThreadSafeClass
{
    public double PropertyA { get; set; }
    public double PropertyB { get; set; }
    public double PropertyC { get; set; }

    private ThreadSafeClass()
    {

    }

    public void ModifyClass()
    {
        // do stuff
    }

    public class Synchronizer
    {
        private ThreadSafeClass instance = new ThreadSafeClass();
        private readonly object locker = new object();

        public void Execute(Action<ThreadSafeClass> action)
        {
            lock (locker)
            {
                action(instance);
            }
        }

        public T Execute<T>(Func<ThreadSafeClass, T> func)
        {
            lock (locker)
            {
                return func(instance);
            }
        }
    }
}

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

var syn = new ThreadSafeClass.Synchronizer();

syn.Execute(inst => { 
    inst.PropertyA = 2.0;
    inst.PropertyB = 2.0;
    inst.PropertyC = 2.0;
});

var a = syn.Execute<double>(inst => {
    return inst.PropertyA + inst.PropertyB;
});
4 голосов
/ 26 января 2010

Я знаю, что это может звучать как умный ** ответ, но ... ЛУЧШИЙ способ разработки многопоточных классов - это на самом деле узнать о многопоточности, о ее последствиях, ее сложностях и что это подразумевает. Там нет серебряной пули.

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

Редактировать: Разумеется, вы должны знать примитивы синхронизации как операционной системы, так и вашего языка (в данном случае C # под Windows, я думаю).

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

3 голосов
/ 26 января 2010

Поскольку, похоже, больше никто этого не делает, вот небольшой анализ вашего конкретного дизайна.

  • Хотите прочитать какой-либо один объект? THREADSAFE
  • Хотите обновить какой-либо одной собственности? THREADSAFE
  • Хотите прочитать одно свойство, а затем обновить его на основе его первоначального значения? Не потокобезопасен

Поток 2 может обновить значение между чтением и обновлением потока 1.

  • Хотите обновить два связанных свойства одновременно? Не защищен от потоков

Вы можете получить свойство A, имеющее значение потока 1, и свойство B, имеющее значение потока 2.

  1. Тема 1 Обновление A
  2. Тема 2 Обновление A
  3. Тема 1 Обновление B
  4. Тема 2 Обновление B

    • Хотите прочитать два связанных свойства одновременно? Не потокобезопасен

Опять же, вы можете быть прерваны между первым и вторым чтением.

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

3 голосов
/ 25 января 2010

Имейте в виду, что термин "потокобезопасный" не является конкретным; то, что вы здесь делаете, более точно будет называться «синхронизацией» посредством использования Monitor блокировки.

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

lock (theLock)
{
    propertyB = value;
}

в это:

lock (theLock) propertyB = value;

Что касается того, является ли это правильным подходом для вас, нам действительно нужно больше информации. Синхронизация - это всего лишь один из подходов к «безопасности потоков»; неизменяемые объекты, семафоры и т. д. - это разные механизмы, подходящие для разных вариантов использования. Для простого примера, который вы предоставляете (где выглядит, как будто вы пытаетесь обеспечить атомарность операции get или set), тогда похоже, что вы сделали правильные вещи, но если ваш код предназначен для иллюстрация, чем пример, тогда все может быть не так просто.

1 голос
/ 26 января 2010

Единственное, что вы можете сделать, чтобы избежать лишнего кода, - это использовать что-то вроде PostSharp для автоматического внедрения этих lock операторов в ваш код, даже если у вас их было сотни. Все, что вам нужно, это один атрибут, прикрепленный к классу, и реализация атрибута, которая добавит дополнительные блокирующие переменные.

1 голос
/ 25 января 2010

Вы можете найти класс Interlocked полезным. Содержит несколько атомарных операций.

0 голосов
/ 25 января 2010

Согласно моему комментарию выше - это немного раздражает, если вы хотите, чтобы одновременные читатели были разрешены, но разрешен только один писатель.Обратите внимание, что если у вас есть .NET 3.5, используйте ReaderWriterLockSlim вместо ReaderWriterLock для этого типа шаблона.

public class ThreadSafeClass
{
    private readonly ReaderWriterLock theLock = new ReaderWriterLock();

    private double propertyA;
    public double PropertyA
    {
        get
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                return propertyA;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        set
        {
            theLock.AcquireWriterLock(Timeout.Infinite);
            try
            {
                propertyA = value;
            }
            finally
            {
                theLock.ReleaseWriterLock();
            }
        }
    }

    private double propertyB;
    public double PropertyB
    {
        get
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                return propertyB;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        set
        {
            theLock.AcquireWriterLock(Timeout.Infinite);
            try
            {
                propertyB = value;
            }
            finally
            {
                theLock.ReleaseWriterLock();
            }
        }
    }

    public void SomeMethod()
    {
        theLock.AcquireWriterLock(Timeout.Infinite);
        try
        {
            theLock.AcquireReaderLock(Timeout.Infinite);
            try
            {
                PropertyA = 2.0 * PropertyB;
            }
            finally
            {
                theLock.ReleaseReaderLock();
            }
        }
        finally
        {
            theLock.ReleaseWriterLock();
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...