Проектирование безопасного слоя

При чтении документации MSDN он всегда позволяет узнать, является ли класс потокобезопасным или нет. Мой вопрос в том, как вы создаете класс, который должен быть потокобезопасным? Я не говорю о вызове класса с блокировкой. Я имею в виду, что я работаю для Microsoft, создавая объект класса XXX, и я хочу сказать, что это "Thread Safe", что мне нужно делать?

Ответы

Ответ 1

Самый простой и надежный способ сделать безопасный поток классов - сделать его immutable. Красота заключается в том, что вам больше не придется беспокоиться о блокировке.

Рецепт: Сделать все переменные экземпляра readonly в С# (final в Java).

  • Неизменяемый объект, созданный и инициализированный в конструкторе, не может быть изменен.
  • Неизменяемый объект является потокобезопасным. Период.
  • Это не то же самое, что иметь класс с только константами.
  • Для изменяемых частей вашей системы вам все равно необходимо учитывать и использовать свойство блокировки/синхронизации. Это одна из причин для того, чтобы в первую очередь писать непреложные классы.

Смотрите также question.

Ответ 2

В дополнение к другим отличным ответам здесь рассмотрим еще один угол.

Недостаточно того, чтобы внутренняя структура данных класса была на 100% надежной, если публичный API имеет многошаговые операции, которые нельзя использовать поточно-безопасным способом.

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

Рассмотрим этот код:

if (list.Count > 0)
{
    var item = list[0];
}

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

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

Одним из способов решения этой проблемы было бы, чтобы автор типа списка рассмотрел типичные сценарии использования и добавил соответствующие методы к типу:

public bool TryGetFirstElement(out T element)

тогда у вас будет:

T element;
if (list.TryGetFirstElement(out element))
{
    ....

предположительно, TryGetFirstElement работает в потокобезопасном режиме и никогда не вернет true одновременно с тем, что он не сможет прочитать значение первого элемента.

Ответ 3

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

Ответ 4

не общие классы ICollection предоставляют свойства безопасности потоков. IsSynchronized и SyncRoot. к сожалению, вы не можете установить IsSynchronized. Подробнее о них вы можете узнать здесь

В ваших классах вы можете иметь что-то похожее на IsSynchronized и Syncroot, выставлять публичные методы/свойства в одиночку и внутри тела, проверяя их. Ваш IsSynchronized будет свойством readonly, поэтому, как только ваш экземпляр будет инициализирован, вы не сможете его изменить.

bool synchronized = true;
var collection = new MyCustomCollection(synchronized);
var results = collection.DoSomething();

public class MyCustomCollection 
{
  public readonly bool IsSynchronized;
  public MyCustomCollection(bool synchronized)
  {
   IsSynchronized = synchronized
  }

  public ICollection DoSomething()
  { 
    //am wondering if there is a better way to do it without using if/else
    if(IsSynchronized)
    {
     lock(SyncRoot)
     {
       MyPrivateMethodToDoSomething();
     }
    }
    else
    {
      MyPrivateMethodToDoSomething();
    }

  }
}

Вы можете больше узнать о написании коллекций потокобезопасных файлов на Блог Jared Parson

Ответ 5

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

Когда в документации указано

Любой публичный статический (общий в Visual Basic) членами этого типа являются потоки сейф.

это, вероятно, означает, что статические члены класса не мутируют общее состояние.

Когда в документации указано

Любые члены экземпляра не гарантированно надежный поток.

это, вероятно, означает, что методы имеют минимальную внутреннюю блокировку.

Когда в документации указано

Все общественные и защищенные члены этот класс является потокобезопасным и может быть используется одновременно из нескольких потоки.

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

Ответ 6

Темабезопасные классы - это защита данных (переменные экземпляра) в вашем классе. Наиболее распространенный способ сделать это - использовать ключевое слово блокировка. Самая распространенная ошибка новичка заключается в том, чтобы использовать блокировку всего класса вместо более мелкой очистки:

lock (this)
{
   //do somethnig
}

Проблема заключается в том, что он может дать вам большой успех, если класс делает что-то важное. Общее правило заключается в том, чтобы зафиксировать как можно меньше как можно меньше времени.

Вы можете прочитать больше здесь: заблокировать ключевое слово в С#

Когда вы решите глубже понять многопоточность, вы также можете взглянуть на ReaderWriterLoch и Семафор. Но я предлагаю вам начать с ключевого слова lock.