Является ли C # '??' оператор поток безопасен? - PullRequest
8 голосов
/ 03 июня 2009

Все знают, что это не потокобезопасно:

public StringBuilder Builder
{
    get 
    {
        if (_builder != null)
            _builder = new StringBuilder();
        return _builder; 
    }
}

А как насчет этого?

public StringBuilder Builder
{
    get { return _builder ?? (_builder = new StringBuilder()); }
}

Ответы [ 6 ]

10 голосов
/ 03 июня 2009

НАЧАТЬ РЕДАКТИРОВАТЬ

Судя по вашему отредактированному заголовку, сам оператор слияния нуля кажется поточно-ориентированным (см. Анализ Фила Хаака ). Похоже, однако, что он не гарантирует от потенциальных множественных вызовов конструктора StringBuilder.

КОНЕЦ РЕДАКТИРОВАНИЯ

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

// below code makes the getter thread safe
private object builderConstructionSynch = new object();
public StringBuilder Builder
{
    get
    {
        lock (builderConstructionSynch)
        {
            if (_builder == null) _builder = new StringBuilder();
        }
        return _builder;
    }
}

Вышеуказанное предотвратит проблему с многопоточностью при отложенной инициализации _builder, но если вы не синхронизируете свои вызовы с методами экземпляра StringBuilder, вам не гарантируется безопасность потоков в любых методах, которые используют свойство Builder. Это связано с тем, что методы экземпляра в StringBuilder не были разработаны для обеспечения безопасности потоков. См. Текст ниже со страницы MSDN StringBuilder .

Любая общедоступная статика (Shared в Visual Основные) члены этого типа являются потоками безопасный. Любые члены экземпляра не являются гарантированно безопасен для потоков.

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

public void AppendString(string toAppend)
{
    lock (Builder)
    {
        Builder.Append(toAppend);
    }
}

Таким образом, вы не пишете код синхронизации повсеместно.

10 голосов
/ 03 июня 2009

Это не более или менее потокобезопасно; у вас все еще могут быть два потока, выполняющих проверку на ноль одновременно, поэтому вы создаете отдельные объекты и не видите другие.

8 голосов
/ 03 июня 2009

НЕТ для обеих версий

2 голосов
/ 03 июня 2009

Я сам не тестировал этот подход, но если вам нужна безопасность потоков без лишних затрат на схему блокировки и вы не беспокоитесь о возможном создании и отбрасывании экземпляра объекта, вы можете попробовать это:

using System.Threading;

public StringBuilder Builder
{
    get 
    {
        if (_builder != null)
            Interlocked.CompareExchange( ref _builder, new StringBuilder(), null );
        return _builder; 
    }
}

Вызов CompareExchange () выполнит атомарную замену значения в _builder новым экземпляром StringBuilder, только если _builder == null. Гарантируется, что все методы класса Interlocked НЕ будут вытеснены переключателями потоков.

2 голосов
/ 03 июня 2009

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

2 голосов
/ 03 июня 2009

Нет, ни один не атомарный

...