Действительно ли это - хороший дизайн для создания безопасных от нити классов в C#?

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

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;
        }
    }
}

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

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

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

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

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

Так, определение класса выше представляют хороший подход? В противном случае, что Вы рекомендовали бы для дизайна с подобным поведением, которое будет безопасно от нити для подобного набора использования?

12
задан Joe H 26 January 2010 в 05:06
поделиться

7 ответов

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

Это пример полуприветмаемого или -программистов-Immymutable класса .

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;
});
8
ответ дан 2 December 2019 в 18:19
поделиться

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

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

lock (theLock)
{
    propertyB = value;
}

в это:

lock (theLock) propertyB = value;

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

3
ответ дан 2 December 2019 в 18:19
поделиться

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

  • Хотите прочитать любое единственное свойство? Threadsafe
  • Хотите обновить в любое одностороннее свойство? ThreadsAfe
  • Хотите прочитать одно свойство, а затем обновить его на основе его исходного значения? Не ThreadSafe

Thread 2 может обновить значение между нитью 1 чтение и обновление.

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

Вы можете в конечном итоге с свойством имеете значение Thread 1 и свойство B, имеющие резьбу 2-качества.

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

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

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

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

3
ответ дан 2 December 2019 в 18:19
поделиться

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

1
ответ дан 2 December 2019 в 18:19
поделиться

Согласно моему комментарию выше - он получает немного волосы, если вы хотите, чтобы одновременные читатели разрешены, но только один писатель разрешен. Примечание, если у вас есть .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();
        }
    }
}
0
ответ дан 2 December 2019 в 18:19
поделиться

Вы можете найти блокировку класс полезным. Он содержит несколько атомных операций.

1
ответ дан 2 December 2019 в 18:19
поделиться

Самый простой способ - если известен тип сборки, на которую имеется ссылка:

AssemblyName name = typeof(MyCompany.MyLibrary.SomeType).Assembly.GetName();

Assembly.GetName возвращает значение AssemblyName , которое имеет свойство Version , указывающее версию сборки.

Кроме того, можно получить имена всех сборок, на которые ссылается исполняющая сборка (т.е. .exe):

AssemblyName[] names = Assembly.GetExecutingAssembly().GetReferencedAssemblies();
-121--2675090-

Чтобы ответить на сборочную часть вопроса, сборка не компилируется в двоичный файл, как я понимаю. Сборка = = = двоичная. Это прямо переводится. Каждая операция сборки имеет двоичную последовательность, которая непосредственно соответствует ей. Каждая операция имеет двоичный код, а каждая переменная регистра имеет двоичный адрес.

То есть, если только Assembler! = Assembly и я не понимаю ваш вопрос.

-121--1099926-

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

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

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

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

4
ответ дан 2 December 2019 в 18:19
поделиться
Другие вопросы по тегам:

Похожие вопросы: