是C#'??'操作员线程安全?

时间:2022-06-16 02:27:02

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

6 个解决方案

#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.

根据您编辑的标题,null-coalescing运算符本身似乎是线程安全的(参见Phil Haack的分析)。但是,它似乎并不能保证不会对StringBuilder构造函数进行多次调用。

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.

线程有一个更大的问题,那就是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;
    }
}

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.

以上将防止_builder的延迟初始化中的线程问题,但除非您同步调用StringBuilder的实例方法,否则在使用Builder属性的任何方法中都不能保证线程安全。这是因为StringBuilder中的实例方法并非设计为线程安全的。请参阅MSDN StringBuilder页面中的以下文本。

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

此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。任何实例成员都不保证是线程安全的。

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:

如果你在多个线程中使用StringBuilder,那么你可能会更好地将它封装在你的类中。使构建器变为私有,并公开您需要的公共方法行为:

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.

只有当_builder == null时,对CompareExchange()的调用才会使用StringBuilder的新实例对_builder中的值进行原子替换。 Interlocked类中的所有方法都被保证不被线程切换抢占。

#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.

根据您编辑的标题,null-coalescing运算符本身似乎是线程安全的(参见Phil Haack的分析)。但是,它似乎并不能保证不会对StringBuilder构造函数进行多次调用。

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.

线程有一个更大的问题,那就是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;
    }
}

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.

以上将防止_builder的延迟初始化中的线程问题,但除非您同步调用StringBuilder的实例方法,否则在使用Builder属性的任何方法中都不能保证线程安全。这是因为StringBuilder中的实例方法并非设计为线程安全的。请参阅MSDN StringBuilder页面中的以下文本。

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

此类型的任何公共静态(在Visual Basic中为Shared)成员都是线程安全的。任何实例成员都不保证是线程安全的。

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:

如果你在多个线程中使用StringBuilder,那么你可能会更好地将它封装在你的类中。使构建器变为私有,并公开您需要的公共方法行为:

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.

只有当_builder == null时,对CompareExchange()的调用才会使用StringBuilder的新实例对_builder中的值进行原子替换。 Interlocked类中的所有方法都被保证不被线程切换抢占。