Что такое потокобезопасность (C #)? (Строки, массивы, ...?) - PullRequest
24 голосов
/ 26 апреля 2011

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

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

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

Is перечисление всегда / всегда потокобезопасно?

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

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

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

РЕДАКТИРОВАТЬ: Я имел в виду, если все темы только чтение и использование (не запись или изменение)объект.(за исключением последнего вопроса, где очевидно, что я имел в виду, если потоки читают и пишут).Потому что я не знаю, является ли простой доступ или перечисление потокобезопасным.

Ответы [ 5 ]

26 голосов
/ 26 апреля 2011

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

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

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

13 голосов
/ 26 апреля 2011

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

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


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


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

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

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


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

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


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

By "Потокобезопасный "Вы имеете в виду, что два потока всегда будут получать согласованные результаты при чтении из списка?Как отмечалось выше, язык C # дает очень ограниченные гарантии для наблюдения результатов при чтении из переменных.Можете ли вы дать точное определение того, что означает «потокобезопасность» для вас при энергонезависимом чтении?

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

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

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

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


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


4 голосов
/ 26 апреля 2011

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

3 голосов
/ 26 апреля 2011

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

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

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

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

С точки зрения перечисления, я буду ссылаться на 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.
0 голосов
/ 26 апреля 2011

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

PS

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

...