Является .NET StringBuilder потокобезопасным
В разделе "Безопасность потока" в документации MSDN для StringBuilder
указано, что:
... любые члены экземпляра не гарантируют безопасность потоков...
но это утверждение похоже, что оно было скопировано и вставлено почти для каждого класса в Framework:
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/
Кроме того, источник StringBuilder, обнаруженный Reflector, и сопровождающие комментарии
в источнике SSCLI, также предлагают множество соображений по внедрению для обеспечения безопасности потоков:
http://labs.developerfusion.co.uk/SourceViewer/browse.aspx?assembly=SSCLI&namespace=System.Text&type=StringBuilder
Есть ли у кого-нибудь более глубокое понимание того, может ли экземпляр StringBuilder
безопасно делиться между несколькими параллельными потоками?
Ответы
Ответ 1
Абсолютно нет; здесь простой пример снят с 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;
}
Атрибут обрабатывает только вызывающие, а не поточную безопасность; это абсолютно не потокобезопасно.
Обновление: глядя на источник, который он ссылается, это явно не текущая кодовая база .NET 4.0 (сравнение нескольких методов). Возможно, он говорит о конкретной версии .NET или, возможно, о XNA, но в целом это не. В 4.0 StringBuilder
не есть a m_currentThread
поле, которое использует исходный материал Gavin; есть подсказка (неиспользуемая константа ThreadIDField
), которую он использовал, но... больше не работает.
Если вы хотите direct disproof - запустите это на 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);
Ответ 2
Весь смысл документации - предоставить вам гарантии. В этом случае члены экземпляра ничего не гарантируют потокобезопасность, и вы должны рассматривать его как таковые, поэтому полагаться на внешние методы синхронизации.
То, что некоторые вещи могут быть потокобезопасными, - это деталь реализации, которая может и, возможно, может изменяться от одной версии фреймворка к следующей или от одной реализации к следующей (на самом деле в таких версиях существует множество таких деталей; У Lippert есть несколько сообщений, в которых подробно описаны некоторые из них). Не полагайтесь на это.
(Другими словами: не записывайте код в реализацию, пишите его по интерфейсу и контракту, которые в этом случае являются метаданными класса и его документации.)
Ответ 3
Из документации MSDN:
Все публичные статические (Shared in Visual Basic) члены этого типа безопасный поток. Любые члены экземпляра не гарантированно являются потоками сейф.