Является ли свойство строки самим потокобезопасным? - PullRequest
30 голосов
/ 12 января 2009

Строки в 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 ]

41 голосов
/ 12 января 2009

В большинстве ответов используется слово «атомарный», как будто атомарные изменения - это все, что нужно. Обычно нет.

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

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

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 или использовать явный барьер памяти. Это все относится и к строковым свойствам.

17 голосов
/ 12 января 2009

Поле get / set (ldfld / stfld) поля ссылочного типа (IIRC) гарантированно является атомарным, поэтому здесь не должно быть никакого риска повреждения. Так что он должен быть поточно-ориентированным с этого угла, но лично я бы заблокировал данные на более высоком уровне - т.е.

lock(someExternalLock) {
    record.Foo = "Bar";
}

или, может быть:

lock(record.SyncLock) {
    record.Foo = "Bar";
}

Это позволяет вам выполнять несколько операций чтения / обновления для одного и того же объекта в качестве атомарной операции, чтобы другие потоки не могли получить недопустимое состояние объекта

4 голосов
/ 12 января 2009

Установка строки является атомарной операцией, то есть вы получите либо новую, либо старую строку, вы никогда не получите мусор.

Если вы делаете какую-то работу, например,

obj.SampleProperty = "Dear " + firstName + " " + lastName;

тогда конкатенация строк происходит перед вызовом set, поэтому sampleField всегда будет либо новой строкой, либо старой.

Если, однако, ваш код конкатенации строк самореферентен, например

obj.SampleProperty += obj.SampleProperty + "a";

и еще где у вас в другом потоке

obj.SampleProperty = "Initial String Value";

Тогда вам нужен замок.

Считайте, что вы работаете с int. Если вы присваиваете int и любое значение, полученное из int, является действительным, вам не нужно его блокировать.

Однако, если int ведет подсчет количества виджетов, обработанных двумя или более потоками, для точного подсчета необходимо заблокировать int. То же самое относится и к строкам.

У меня такое чувство, что я не очень хорошо объяснил это, надеюсь, это поможет.

Спасибо

BW

0 голосов
/ 12 января 2009

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

Однако, если получить и установить поле ссылочного типа в качестве свойства, подобного этому, то добавление оператора блокировки не добавляет никакого значения. Назначения указателей гарантированно будут атомарными в среде .NET, и если несколько потоков изменяют свойство, то в любом случае у вас есть внутреннее состояние гонки (где потоки могут видеть разные значения; это может быть или не быть проблемой), поэтому есть немного точка в замке.

Так что для того, что он делает, первый кусок кода в порядке. Но действительно ли вы хотите встроить характерные условия гонки в многопоточное приложение - это другой вопрос.

0 голосов
/ 12 января 2009

Это потокобезопасный без необходимости блокировки. Строки являются ссылочными типами, поэтому изменяется только ссылка на строку. Ссылки относятся к типу гарантированно атомарному (Int32 в 32-битных системах и Int64 в 64-битном).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...