【问题标题】:C# Parallel loop local variable thread safe InformationC#并行循环局部变量线程安全信息
【发布时间】:2017-01-05 11:01:28
【问题描述】:

如果我以这种方式使用Parallel.For,我想知道List<object> 是否是线程安全的。请看下面:

public static List<uint> AllPrimesParallelAggregated(uint from, uint to)
        {
            List<uint> result = new List<uint>();
            Parallel.For((int)from, (int)to,
                () => new List<uint>(), // Local state initializer
                (i, pls, local) =>      // Loop body
                {
                    if (IsPrime((uint)i))
                    {
                        local.Add((uint)i);
                    }
                    return local;
                },
                local =>                // Local to global state combiner
                {
                    lock (result)
                    {
                        result.AddRange(local);
                    }
                });
            return result;
        }

local 列表是线程安全的吗?我是否在result 列表中没有数据的正确数据由于多个线程而被改变,因为我使用的是正常循环?

注意:我不担心列表顺序。我想知道列表和数据的长度。

【问题讨论】:

  • List 不是并行线程安全的,尤其是您想要的。
  • 所以,这意味着我的result 列表中的数据与我从正常循环中获取的数据不同?
  • 这意味着.....List不是线程安全的!!
  • 结果中的数字顺序不太可能总是与单线程运行时相同或相同。既然你 lock(result)local 是异步操作的结果,你的组合函数应该可以工作。
  • @Saadi 列表的长度应该与计算单线程时相同。也许单线程版本会更快,即使您为每个单个素数创建一个新列表。您宁愿让任务计算 prims 的范围,然后最终组合这些子范围。嗯...不知道我最后的陈述是否正确。取决于那个并行的东西是如何工作的。

标签: c# asp.net .net multithreading task-parallel-library


【解决方案1】:

这个解决方案是线程安全的吗?技术上是的,实际上不是。

线程安全List(与队列或袋子相反)的概念是列表对于order 或者更严格地说 是安全的index,因为除了升序整数之外没有键。在平行世界中,当您考虑它时,它是一种荒谬的概念。这就是为什么System.Collections.Concurrent 命名空间包含ConcurrentBagConcurrentQueue 但没有ConcurrentList

由于您在询问列表的线程安全性,我假设您的软件要求是生成按升序排列的列表。如果是这种情况,不,您的解决方案将不起作用。虽然代码在技术上是线程安全的,但线程可以按任何顺序完成,并且您的变量 result 最终将未排序。

如果您希望使用并行计算,您必须将结果存储在一个包中,然后在所有线程完成后对包进行排序以生成有序列表。否则,您必须按顺序执行计算。

而且既然你无论如何都要使用包,那么你也可以使用ConcurrentBag,这样你就不必为lock{}声明而烦恼了。

【讨论】:

  • 是的。如果我不喜欢 PLINQ 来处理这类事情,ConcurrentBag 可能会出现在我的解决方案中。
【解决方案2】:

List 不是线程安全的。但是您当前的算法有效。正如其他答案和 cmets 中所解释的那样。

这是关于 For.Parallel 的 localInit 的描述

&lt;paramref name="localInit"/&gt; 委托为参与循环执行的每个线程调用一次,并返回每个线程的初始本地状态。这些初始状态被传递给第一个


IMO 您在循环中添加了不必要的复杂性。我会改用ConcurrentBag。这在设计上是线程安全的。

ConcurrentBag<uint> result = new ConcurrentBag<uint>();
Parallel.For((long) from, (long) to,
    (i, PLS) =>
    {
        if (IsPrime((uint)i))
        {
            result.Add((uint)i); // this is thread safe. don't worry
        }
    });
return result.OrderBy(I => I).ToList(); // order if that matters

See concurrent bag here

ConcurrentBag 的所有公共和受保护成员都是线程安全的,可以从多个线程同时使用。

【讨论】:

  • 感谢您的回答,但我的问题是每个人都说 List 不是线程安全的。你怎么能说result.Add((uint)i); 是线程安全的?
  • 这是 ConcurrentBag。不是列表。 ConcurrentBag 就是为此目的而设计的。
【解决方案3】:

List&lt;T&gt; 不是线程安全的。对于您使用它的方式,虽然不需要线程安全。当您从多个线程同时访问资源时,您需要线程安全。您没有这样做,因为您正在处理本地列表。

最后,您将本地列表的内容添加到 result 变量中。由于您使用lock 进行此操作,因此您在该块内是线程安全的。

所以你的解决方案可能没问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 2017-03-07
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多