似乎ConcurrentBag不是线程安全的

Par*_*rsa 1 c# multithreading

我编写了一个程序,列表构建器方法返回包含大量字符串(1milion项)的字符串的IEnumerable,并将其存储在字符串List中,然后将所有项附加到Parallel.Foreach中的StringBuilder实例中.然后我打印了stringBuilderInstance.Length.

问题是它少于1000000.经过一些谷歌搜索,我意识到列表集合不是线程安全的,这导致了这个问题.因此2解决方案贯穿我的脑海:

1)使用

2)使用ConcurrentBag

当我使用锁定时,它是可以的,长度是1百万,但是:

当我使用字符串的ConcurrentBag时,长度比我预期的要少!

这个问题的根本原因是什么?

List-Creator方法:

public static List<string> CreateList()
{
    List<string> result = new List<string>();
    for (int i = 0; i < 1000000; i++)
    {
        result.Add(1.ToString());
    }
    return result;
}
Run Code Online (Sandbox Code Playgroud)

使用ConcurrentBag:

public static void DoWithParallel_ThreadSafe()
{
    ConcurrentBag<string> listOfString = new ConcurrentBag<string>(CreateList());
    StringBuilder a = new StringBuilder();
    Action<string> appender = (number) =>
    {
        a.Append(number);
    };
    Parallel.ForEach(listOfString, appender);
    Console.WriteLine($"The string builder lenght : {a.Length}");
}
Run Code Online (Sandbox Code Playgroud)

使用锁:

public static void DoWithParallel_UnsafeThread_Lock()
{
    List<string> listOfString = CreateList();
    StringBuilder a = new StringBuilder();
    Action<string> appender = (number) =>
    {
        lock (listOfString)
        {
            a.Append(number);
        }
    };
    Parallel.ForEach(listOfString, appender);
    Console.WriteLine($"The string builder lenght : {a.Length}");
}
Run Code Online (Sandbox Code Playgroud)

主要:

static void Main(string[] args)
{
    DoWithParallel_UnsafeThread_Lock();
    DoWithParallel_ThreadSafe();
    Console.ReadKey();
}
Run Code Online (Sandbox Code Playgroud)

先感谢您.

Ser*_*rvy 6

StringBuilder不能从多个线程变异,因此当你尝试这样做时代码不起作用.请注意,锁定毫无意义; 因为无法从多个线程完成工作,所以不要创建多个线程来完成工作.因为你永远不会ConcurrentBag从多个线程访问,所以使用它而不是使用它是没有意义的List.


Aas*_*set 5

StringBuilder不是线程安全的,这就是为什么有些Append()呼叫“丢失”。所以你仍然需要锁,即使你的集合是线程安全的。

(另外,请参阅 Servy 的回答,了解为什么您根本不需要集合是线程安全的。)

  • @Parsa:字符串是不可变的;您的变量不是线程安全的。 (4认同)
  • 你不应该锁在这里。正确的解决方案是不要创建多个线程,而不是创建一堆线程只是为了同步所有线程。 (3认同)