Подтвердить что ты не робот

Что такое потокобезопасность (С#)? (Строки, массивы,...?)

Я новичок в С#, поэтому, пожалуйста, несите меня. Я немного путаюсь с безопасностью потока. Когда что-то потокобезопасно, а когда что-то не так?

Является чтением (только чтение из чего-то, что было инициализировано ранее) из поля, всегда безопасного потока?

//EXAMPLE
RSACryptoServiceProvider rsa = new RSACrytoServiceProvider();
rsa.FromXmlString(xmlString);  
//Is this thread safe if xml String is predifined 
//and this code can be called from multiple threads?

Является доступ к объекту из массива или списку всегда потокобезопасным (если вы используете цикл for для перечисления)?

//EXAMPLE (a is local to thread, array and list are global)
int a = 0;
for(int i=0; i<10; i++)
{
  a += array[i];
  a -= list.ElementAt(i);
}

Является перечислением всегда/когда-либо потокобезопасным?

//EXAMPLE
foreach(Object o in list)
{
   //do something with o
 }

Может ли писать и читать в какое-либо конкретное поле когда-либо приводить к поврежденному чтению (половина поля изменяется, а половина по-прежнему остается неизменной)?

Спасибо за все ваши ответы и время.

EDIT: Я имел в виду, что все потоки только читают и используют (не записывая или изменяя) объект. (за исключением последнего вопроса, когда очевидно, что я имел в виду, что потоки читаются и записываются). Потому что я не знаю, является ли простой доступ или перечисление потокобезопасным.

4b9b3361

Ответ 1

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

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

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

Ответ 2

Является ли чтение (только чтение из чего-то, что было инициализировано ранее) из поля, всегда безопасного потока?

Язык С# гарантирует, что чтение и запись упорядочены, когда чтение и запись находятся в одном потоке в разделе 3.10:


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


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

Поэтому вопрос невозможен, поскольку содержит слово undefined. Можете ли вы дать точное определение того, что "прежде" означает для вас в отношении событий в многопоточной многопроцессорной системе?

Язык гарантирует, что побочные эффекты упорядочиваются только в отношении критических точек выполнения, и даже тогда не дает никаких сильных гарантий при использовании исключений. Опять же, чтобы процитировать из раздела 3.10:


Выполнение программы С# продолжается так, что побочные эффекты каждого исполняемого потока сохраняются в критических точках выполнения. Побочный эффект определяется как чтение или запись изменчивого поля, запись в энергонезависимую переменную, запись на внешний ресурс и выброс исключения. Критические точки выполнения, в которых должен сохраняться порядок этих побочных эффектов, - это ссылки на изменчивые поля, операторы блокировки и создание и прекращение потоков. [...] Упорядочение побочных эффектов сохраняется в отношении неустойчивых чтений и записей.

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


Является ли доступ к объекту из массива или списку всегда потокобезопасным (в случае, если вы используете цикл for для перечисления)?

Под "thread safe" вы подразумеваете, что два потока всегда будут отслеживать согласованные результаты при чтении из списка? Как отмечалось выше, язык С# дает очень ограниченные гарантии относительно наблюдения результатов при чтении из переменных. Можете ли вы дать точное определение того, что означает "потокобезопасность" для вас в отношении долговременного чтения?

Является ли перечисление всегда/когда-либо безопасным?

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

Может ли запись и чтение в конкретное поле когда-либо приводить к поврежденному чтению (половина поля изменяется, а половина по-прежнему остается неизменной)?

Да. Я отсылаю вас к разделу 5.5, в котором говорится:


Считывание и запись следующих типов данных: atomic: bool, char, byte, sbyte, short, ushort, uint, int, float и reference. Кроме того, чтение и запись типов перечислений с базовым типом в предыдущем списке также являются атомарными. Чтения и записи других типов, включая длинные, улоновые, двойные и десятичные, а также определяемые пользователем типы, не гарантируются как атомарные. Помимо библиотечных функций, предназначенных для этой цели, нет гарантии атомарного чтения-модификации-записи, например, в случае приращения или уменьшения.


Ответ 3

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

Ответ 4

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

Если вы должны это сделать, вы можете сделать:

lock(i){
    i.GetElementAt(a)
}

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

В терминах перечисления я буду ссылаться на MSDN:

The enumerator does not have exclusive access to the collection; therefore, enumerating 
through a collection is intrinsically not a thread-safe procedure. To guarantee thread 
safety during enumeration, you can lock the collection during the entire enumeration. To 
allow the collection to be accessed by multiple threads for reading and writing, you must     
implement your own synchronization.

Ответ 5

Пример отсутствия безопасности потока: когда несколько потоков увеличивают целое число. Вы можете настроить его таким образом, чтобы у вас было предопределенное количество приращений. Однако, что вы наблюдаете, является то, что int не увеличивался так сильно, как вы думали. Случается, что два потока могут увеличивать одно и то же значение целого числа. Это всего лишь пример aplethora эффектов, которые вы можете наблюдать при работе с несколькими потоками.

PS

Потоковое безопасное увеличение доступно через Interlocked.Increment(ref i)