.NET的StringBuilder是否是线程安全的

Jas*_*ome 30 .net stringbuilder thread-safety

MSDN文档的常规"线程安全"部分,用于StringBuilder说明:

...任何实例成员都不保证是线程安全的......

但是这个陈述感觉它已被复制并粘贴到框架中的几乎每个类:

http://msdn.microsoft.com/en-us/library/system.text.stringbuilder.aspx

但是,Gavin Pugh的这些博客文章提到了以下的线程安全行为StringBuilder:

http://www.gavpugh.com/2010/03/23/xnac-stringbuilder-to-string-with-no-garbage/

http://www.gavpugh.com/2010/04/01/xnac-avoiding-garbage-when-working-with-stringbuilder/

此外,Reflector揭示的StringBuilder的源代码以及SSCLI源代码中的相应注释也提出了许多实现注意事项以确保线程安全性:

http://labs.developerfusion.co.uk/SourceViewer/browse.aspx?assembly=SSCLI&namespace=System.Text&type=StringBuilder

有没有人更深入地了解一个StringBuilder实例是否可以安全地在多个并发线程之间共享?

Mar*_*ell 25

绝对不; 这是一个简单的例子,从4.0通过反射器提升:

[SecuritySafeCritical]
public StringBuilder Append(char value)
{
    if (this.m_ChunkLength < this.m_ChunkChars.Length)
    {
        this.m_ChunkChars[this.m_ChunkLength++] = value;
    }
    else
    {
        this.Append(value, 1);
    }
    return this;
}
Run Code Online (Sandbox Code Playgroud)

该属性只处理调用者,而不是线程安全; 这绝对不是线程安全的.

更新:查看他引用的源代码,这显然不是当前的.NET 4.0代码库(比较几种方法).也许他正在谈论一个特定的.NET版本,或者可能是XNA - 但事实并非如此.4.0 StringBuilder具有一个m_currentThread领域,加文的源材料使用; ThreadIDField曾经存在一个提示(一个未使用的常数),但是......不再存在.


如果你想直接反对 - 在4.0上运行它; 它很可能会给出错误的长度(我在4k区域看到了一些,在2k区域看到了一些 - 它应该正好是5000),但是其他一些Append方法(Append(char)例如)更倾向于抛出异常,具体取决于在时间上:

var gate = new ManualResetEvent(false);
var allDone = new AutoResetEvent(false);
int counter = 0;
var sb = new StringBuilder();
ThreadStart work = delegate
{
    // open gate when all 5 threads are running
    if (Interlocked.Increment(ref counter) == 5) gate.Set();
    else gate.WaitOne();

    for (int i = 0; i < 1000; i++) sb.Append("a");

    if (Interlocked.Decrement(ref counter) == 0) allDone.Set();
};
for(int i = 0 ; i < 5 ; i++)
{
    new Thread(work).Start();
}
allDone.WaitOne();
Console.WriteLine(sb.Length);
Run Code Online (Sandbox Code Playgroud)


Joe*_*oey 6

文档的重点是为您提供保证.在这种情况下,在实例成员上没有任何东西可以保证是线程安全的,你应该这样对待它,因此依赖于外部同步方法.

有些事情可能是线程安全的是一个实现细节,它可以并且可能确实从框架的一个版本变为下一个版本或从一个实现到下一个实现(实际上框架版本中有大量此类细节发生变化; Eric Lippert有一些详细介绍其中几个的帖子).不要依赖它.

(换句话说:不要将代码写入实现,将其写入接口和契约,这是本类的元数据及其文档.)