web-dev-qa-db-fra.com

Utilisation des directives AsParallel () / Parallel.ForEach ()?

Vous cherchez un petit conseil sur l'utilisation de AsParallel() ou Parallel.ForEach() pour accélérer cela.

Voir la méthode que j'ai (simplifiée/bâtarde pour cet exemple) ci-dessous.

Il prend une liste comme "US, FR, APAC", où "APAC" est un alias pour peut-être 50 autres pays "US, FR, JP, IT, GB", etc. La méthode doit prendre "US, FR, APAC" et la convertir en une liste de "US", "FR", plus tous les pays qui se trouvent dans "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();
}

Est-ce que rendre cela parallélisé aussi simple que de le changer pour ce qui est ci-dessous? Y a-t-il plus de nuances à utiliser AsParallel() que cela? Dois-je utiliser Parallel.ForEach() au lieu de foreach? Quelles règles générales dois-je utiliser lors de la parallélisation de boucles 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();
}
42
SnickersAreMyFave

Plusieurs points.

écrire simplement countriesAndAliases.AsParallel() est inutile. AsParallel() fait partie de la requête Linq qui vient après son exécution en parallèle. La partie est vide, donc pas du tout utile.

en général, vous devez remplacer foreach par Parallel.ForEach(). Mais attention au code non thread-safe! Tu l'as. Vous ne pouvez pas simplement l'envelopper dans foreach car List<T>.Add n'est pas thread-safe lui-même.

donc vous devriez faire comme ça (désolé, je n'ai pas testé, mais ça compile):

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

Modifier :

Vous devez être sûr de deux autres choses:

  1. IsCountryNotAlias doit être thread-safe. Ce serait encore mieux si c'est fonction pure .
  2. Personne ne modifiera AliasCountryLists en attendant, car les dictionnaires ne sont pas sûrs pour les threads. Ou utilisez ConcurrentDictionary pour être sûr.

Liens utiles qui vous aideront:

Modèles pour la programmation parallèle: comprendre et appliquer des modèles parallèles avec le .NET Framework 4

Programmation parallèle dans les directives de codage .NET 4

Quand dois-je utiliser Parallel.ForEach? Quand dois-je utiliser PLINQ?

[~ # ~] ps [~ # ~] : Comme vous le voyez, les nouvelles fonctionnalités parallèles ne sont pas aussi évidentes qu'elles le paraissent (et se sentent).

68
Andrey

Lorsque vous utilisez AsParallel (), vous devez vous assurer que votre corps est protégé contre les threads. Malheureusement, le code ci-dessus ne fonctionnera pas. List<T> N'est pas sûr pour les threads, donc votre ajout de AsParallel() provoquera une condition de concurrence critique.

Si, cependant, vous changez vos collections pour utiliser une collection dans System.Collections.Concurrent , comme ConcurrentBag<T> , le code ci-dessus fonctionnera très probablement.

13
Reed Copsey

Je préférerais utiliser une autre structure de données comme un ensemble pour chaque alias, puis utiliser l'union d'ensemble pour les fusionner.

Quelque chose comme ça

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

Remarque: le code doit être considéré comme un pseudo-code.

3
cjg

Cela me semble être une opération intrinsèquement sérielle. Tout ce que vous faites, c'est parcourir une liste de chaînes et les insérer dans une autre liste. Les bibliothèques de parallélisation vont le faire, plus un tas de threads et de synchronisation - cela finirait probablement plus lentement.

Vous devez également utiliser un HashSet<string> si vous ne voulez pas de doublons.

0
Steve M