Is the C# '??' operator thread safe?

2019-03-26 14:35发布

问题:

Everyone knows that this is not thread safe:

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

What about this?

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

回答1:

BEGIN EDIT

Based on your edited title, the null-coalescing operator itself seems to be thread-safe (see Phil Haack's analysis). It appears, however, that it doesn't guarantee against the potential multiple calls to the StringBuilder constructor.

END EDIT

You have a larger problem with threading, and that is that the Builder property itself represents state that can be shared across threads. Even if you make the lazy initialization thread safe, there's no guarantee that methods consuming Builder are doing it in a thread safe manner.

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

The above will prevent the threading problem in the lazy initialization of _builder, but unless you synchronize your calls to instance methods of StringBuilder, you're not guaranteed thread safety in any methods that consume the Builder property. This is because instance methods in StringBuilder weren't designed to be thread safe. See the below text from the MSDN StringBuilder page.

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

If you're consuming StringBuilder in multiple threads, you're probably better served encapsulating it in your class. Make Builder private and expose what behavior you need as a public method:

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

This way you're not writing synchronization code all over the place.



回答2:

That is no more or less thread-safe; you could still have two threads do the null check at the same time, thus create separate objects and not see the other.



回答3:

NO for both versions



回答4:

No, neither are atomic



回答5:

The answers given are correct, both are not threadsafe. In fact, they are mostly equivalent, and the ?? operator is just compiler magic to make the code leaner. You need to use some synchronization mechanism if you want this to become threadsafe.



回答6:

I have not tested this approach myself, but if you want thread safety without the overhead of a locking scheme and you aren't worried about potentially creating and discarding an object instance, you could try this:

using System.Threading;

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

The call to CompareExchange() will perform an atomic replacement of the value in _builder with a new instance of StringBuilder only if _builder == null. All of the methods on the Interlocked class are gauranteed to NOT be preempted by thread switches.