似乎ConcurrentBag不是线程安全的

时间:2017-07-31 20:46:31

标签: c# multithreading

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

问题在于它少于1000000。 在一些googling之后,我意识到List集合不是线程安全的,这导致了这个问题。 因此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;
}

使用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}");
}

使用锁定:

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

主要:

static void Main(string[] args)
{
    DoWithParallel_UnsafeThread_Lock();
    DoWithParallel_ThreadSafe();
    Console.ReadKey();
}

提前谢谢。

2 个答案:

答案 0 :(得分:6)

StringBuilder无法从多个线程中进行变异,因此当您尝试执行此操作时,代码无法正常工作。请注意,锁定毫无意义;因为工作不能从多个线程完成,所以不要创建多个线程来完成工作。由于您永远不会从多个主题访问ConcurrentBag,因此使用它而不是List毫无意义。

答案 1 :(得分:4)

StringBuilder 是线程安全的,这就是为什么某些Append()次调用“丢失”的原因。所以你仍然需要锁定,即使你的集合是线程安全的。

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