使用AsParallel()/ Parallel.ForEach()指南?

时间:2010-09-23 17:15:54

标签: c# .net multithreading parallel-processing

寻找有关利用AsParallel()Parallel.ForEach()加快提升速度的建议。

请参阅下面的方法(本例中简化/标准化)。

它需要一个像“US,FR,APAC”这样的列表,其中“APAC”是50个其他“US,FR,JP,IT,GB”等的别名。该方法应采用“US,FR,APAC”,并将其转换为“US”,“FR”列表以及“APAC”中的所有国家/地区。

private IEnumerable<string> Countries (string[] countriesAndAliases)
{
    var countries = new List<string>();

    foreach (var countryOrAlias in countriesAndAliases)
    {
        if (IsCountryNotAlias(countryOrAlias))
        {
            countries.Add(countryOrAlias);
        }
        else 
        {
            foreach (var aliasCountry in AliasCountryLists[countryOrAlias]) 
            {
                countries.Add(aliasCountry);
            }
        }
    }

    return countries.Distinct();
}

将此并行化变得如此简单,只需将其更改为以下内容即可?使用AsParallel()比使用更加细微差别吗?我应该使用Parallel.ForEach()代替foreach吗?在并行化foreach循环时,我应该使用哪些经验法则?

private IEnumerable<string> Countries (string[] countriesAndAliases)
{
    var countries = new List<string>();

    foreach (var countryOrAlias in countriesAndAliases.AsParallel())
    {
        if (IsCountryNotAlias(countryOrAlias))
        {
            countries.Add(countryOrAlias);
        }
        else 
        {
            foreach (var aliasCountry in AliasCountryLists[countryOrAlias].AsParallel()) 
            {
                countries.Add(aliasCountry);
            }
        }
    }

    return countries.Distinct();
}

4 个答案:

答案 0 :(得分:66)

有几点。

仅仅写countriesAndAliases.AsParallel()是没用的。 AsParallel()成为Linq查询的一部分,该查询是在并行执行后生成的。部分是空的,所以根本没用。

通常您应该使用foreach重新Parallel.ForEach()。但要注意不是线程安全的代码!你拥有了它。您不能将其包装到foreach中,因为List<T>.Add本身不是线程安全的。

所以你应该这样做(对不起,我没有测试,但它编译):

        return countriesAndAliases
            .AsParallel()
            .SelectMany(s => 
                IsCountryNotAlias(s)
                    ? Enumerable.Repeat(s,1)
                    : AliasCountryLists[s]
                ).Distinct();

修改

你必须确定另外两件事:

  1. IsCountryNotAlias必须是线程安全的。如果它是pure function,那就更好了。
  2. 同时没有人会修改AliasCountryLists,因为字典不是线程安全的。或者使用ConcurrentDictionary来确定。
  3. 有用的链接可以帮助您:

    Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4

    Parallel Programming in .NET 4 Coding Guidelines

    When Should I Use Parallel.ForEach? When Should I Use PLINQ?

    PS :正如您所看到的那样,新的并行功能并不像它们看起来那样明显(和感觉)。

答案 1 :(得分:13)

使用AsParallel()时,您需要确保您的身体是线程安全的。不幸的是,上面的代码不起作用。 List<T>不是线程安全的,因此添加AsParallel()会导致竞争条件。

但是,如果您将集合切换为使用System.Collections.Concurrent中的集合,例如ConcurrentBag<T>,则上述代码很可能会有效。

答案 2 :(得分:3)

我更喜欢为每个别名使用另一个数据结构,比如Set,然后使用Set union来合并它们。

像这样的东西

public string[] ExpandAliases(string[] countries){
    // Alias definitions
    var apac = new HashSet<string> { "US", "FR", ...};
    ... 

    var aliases = new HashMap<string, Set<string>> { {"APAC": apac}, ... };

    var expanded = new HashSet<string>
    foreach(var country in countries){
        if(aliases.Contains(country)
            expanded.Union(aliases[country]);
        else{
            expanded.Add(country);
    }

    return expanded.ToArray();
}

注意:代码应被视为伪代码。

答案 3 :(得分:0)

这对我来说似乎是一种固有的连续操作。您所做的只是循环遍历字符串列表并将它们插入另一个列表中。并行化库将会这样做,加上一堆线程和同步 - 它可能会变慢。

此外,如果您不想要重复,则应该使用HashSet<string>