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

时间:2009-06-03 13:17:02

标签: c# .net thread-safety null-coalescing-operator

每个人都知道这不是线程安全的:

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

这个怎么样?

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

6 个答案:

答案 0 :(得分:10)

这不是更多或更少线程安全;你仍然可以让两个线程同时进行空检查,从而创建单独的对象而不会看到另一个。

答案 1 :(得分:10)

开始编辑

根据您编辑的标题,null-coalescing运算符本身似乎是线程安全的(请参阅Phil Haack's analysis)。但是,它似乎并不能保证不会对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 page

中的以下文字
  

任何公共静态(在Visual中共享)   基本)这种类型的成员是线程   安全。任何实例成员都不是   保证是线程安全的。

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

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

这样你就不会在整个地方编写同步代码。

答案 2 :(得分:8)

两个版本都没有

答案 3 :(得分:2)

不,也不是原子

答案 4 :(得分:2)

给出的答案是正确的,两者都不是线程安全的。事实上,它们大多是等价的,??运算符只是编译器的魔力,使代码更精简。如果你想让它成为线程安全的话,你需要使用一些同步机制。

答案 5 :(得分:2)

我自己没有测试过这种方法,但是如果你想要线程安全而没有锁定方案的开销而且你不担心可能会创建和丢弃一个对象实例,你可以试试这个:

using System.Threading;

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

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