【问题标题】:It seems ConcurrentBag is not Thread-Safe似乎 ConcurrentBag 不是线程安全的
【发布时间】:2018-06-26 16:29:25
【问题描述】:

我编写了一个程序,列表生成器方法返回 IEnumerable 字符串,包括大量字符串(100 万个项目),并将其存储在 List of string 中,然后将 StringBuilder 实例中的所有项目附加到 Parallel.Foreach。然后我打印了 stringBuilderInstance.Length

问题是它少于 1000000 。 在googling 之后,我意识到 List 集合不是线程安全的,这导致了这个问题。 所以我想到了 2 个解决方案:

1) 使用锁定

2) 使用 ConcurrentBag

当我使用 lock 时,没问题,长度是 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;
}

使用并发包:

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

提前谢谢你。

【问题讨论】:

  • 在尝试编写线程安全代码之前,您需要了解有关线程的更多信息。
  • 只是让您知道 Parallel.ForEach 以线程安全的方式调用您传递给第一个参数的 IEnumerable,因此您为该参数传递的集合并不重要。
  • @Scott Chamberlain:如果 Parallel.Foreach 以线程安全的方式调用 IEnumerable,为什么会出现这个问题?
  • @Parsa 你读过这两个答案了吗?问题不是IEnumerable引起的,是StringBuilder引起的。

标签: c# multithreading


【解决方案1】:

StringBuilder 不能从多个线程中变异,因此当您尝试这样做时代码无法正常工作。请注意,锁定是没有意义的;只是不要首先创建多个线程来完成工作,因为无法从多个线程完成工作。由于您永远不会从多个线程访问 ConcurrentBag,因此使用它而不是 List 毫无意义。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

    • 你不应该在这里锁定。正确的解决方案是不创建多个线程,而不是创建一堆线程只是为了同步所有线程。
    • 但是当我使用 string 而不是 stringbuilder 时,它也给了我错误的答案,字符串是线程安全的。我说的对吗?
    • @Parsa:字符串是不可变的;你的变量不是线程安全的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-05
    • 2015-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多