Является ли свойство строки самим потокобезопасным?
Строки в C# являются неизменяемыми и безопасными для потоков. Но что, когда у вас есть публичная собственность геттера? Как это:
public String SampleProperty{
get;
private set;
}
Если у нас есть два потока, и первый вызывает 'get', а второй вызывает 'set' в одно и то же время, что произойдет?
ИМХО, набор должен сделать блокировку поточно-ориентированной:
private string sampleField;
private object threadSafer = new object();
public String SampleProperty{
get{ return this.sampleField; }
private set{
lock(threadSafer){
sampleField = value;
}
}
}
5 ответов
В большинстве ответов используется слово "атомарный", как будто атомарные изменения - это все, что нужно. Обычно нет.
Это упоминалось в комментариях, но не всегда в ответах - это единственная причина, по которой я предоставляю этот ответ. (Точка о блокировке при более грубой детализации, позволяющей добавлять такие вещи, как добавление, также полностью уместна.)
Обычно вы хотите, чтобы поток чтения видел последнее значение переменной / свойства. Это не гарантируется атомарностью. В качестве быстрого примера, вот плохой способ остановить поток:
class BackgroundTaskDemo
{
private bool stopping = false;
static void Main()
{
BackgroundTaskDemo demo = new BackgroundTaskDemo();
new Thread(demo.DoWork).Start();
Thread.Sleep(5000);
demo.stopping = true;
}
static void DoWork()
{
while (!stopping)
{
// Do something here
}
}
}
DoWork
вполне может зацикливаться вечно, несмотря на то, что запись в логическую переменную является атомарной - ничто не мешает JIT кэшировать значение stopping
в DoWork
, Чтобы это исправить, нужно либо заблокировать, сделать переменную volatile
или используйте явный барьер памяти. Это все относится и к строковым свойствам.
Поле get/set (ldfld/stfld) поля ссылочного типа (IIRC) гарантированно является атомарным, поэтому здесь не должно быть никакого риска повреждения. Так что это должно быть поточно-ориентированным с этой точки зрения, но лично я бы заблокировал данные на более высоком уровне - т.е.
lock(someExternalLock) {
record.Foo = "Bar";
}
или, может быть:
lock(record.SyncLock) {
record.Foo = "Bar";
}
Это позволяет вам делать несколько чтений / обновлений одного и того же объекта в качестве атомарной операции, чтобы другие потоки не могли получить недопустимое состояние объекта
Установка строки является атомарной операцией, т.е. вы получите либо новую, либо старую строку, вы никогда не получите мусор.
Если вы делаете какую-то работу, например
obj.SampleProperty = "Dear " + firstName + " " + lastName;
тогда конкатенация строк происходит до вызова set, поэтому sampleField всегда будет либо новой строкой, либо старой.
Однако, если ваш код конкатенации строк самореферентен, например
obj.SampleProperty += obj.SampleProperty + "a";
а еще где на другой ветке у тебя
obj.SampleProperty = "Initial String Value";
Тогда вам нужен замок.
Считайте, что вы работаете с Int. Если вы присваиваете int, и любое значение, полученное из int, является действительным, вам не нужно его блокировать.
Однако, если int ведет подсчет количества виджетов, обработанных двумя или более потоками, для точного подсчета необходимо заблокировать int. Это та же самая ситуация для строк.
У меня такое чувство, что я не очень хорошо это объяснил, надеюсь, это поможет.
Спасибо
BW
Это потокобезопасно без необходимости блокировки. Строки являются ссылочными типами, поэтому изменяется только ссылка на строку. Ссылки относятся к типу гарантированно атомарному (Int32 в 32-битных системах и Int64 в 64-битных).
Ваш второй пример кода определенно не верен, потому что блокировки имеют желаемый эффект только тогда, когда они используются во всех местах доступа к переменной (как для получения, так и для установки), поэтому get
также потребуется замок.
Однако при получении и установке поля ссылочного типа в качестве свойства, подобного этому, добавление оператора блокировки не добавляет никакого значения. Назначения указателей гарантированно являются атомарными в среде.NET, и если несколько потоков изменяют свойство, то в любом случае у вас есть внутреннее состояние гонки (где потоки могут видеть разные значения; это может быть или не быть проблемой), поэтому мало точка в замке.
Так что для того, что он делает, первый кусок кода в порядке. Но действительно ли вы хотите встроить характерные условия гонки в многопоточное приложение - это другой вопрос.