Question

I ai une boucle de Parallel.ForEach exécutant une opération intensive à l'intérieur du corps.

L'opération peut utiliser une table de hachage pour stocker les valeurs, et peut être réutilisée pour d'autres éléments de boucle consécutifs. Ajouter à la Hashtable après l'opération intensive est terminée, l'élément suivant de la boucle peut rechercher dans la Hashtable et de réutiliser l'objet, au lieu de courir à nouveau l'opération intensive.

Cependant, parce que je me sers Parallel.ForEach il y a un problème dangereux, ce qui provoque l'Hashtable.Add et les appels ContainsKey (clés) ne sont pas synchrones, car ils pourraient être en cours d'exécution en parallèle. Présentation de verrous peut provoquer des problèmes perf.

Voici l'exemple de code:

Hashtable myTable = new Hashtable;
Parallel.ForEach(items, (item, loopState) =>
{
    // If exists in myTable use it, else add to hashtable
    if(myTable.ContainsKey(item.Key))
    {
       myObj = myTable[item.Key];
    }
    else
    {
       myObj = SomeIntensiveOperation();
       myTable.Add(item.Key, myObj); // Issue is here : breaks with exc during runtime
    }
    // Do something with myObj
    // some code here
}

Il doit y avoir une API, paramètre de propriété à l'intérieur de la bibliothèque TPL, qui pourrait gérer ce scénario. Est-il?

Était-ce utile?

La solution

Vous cherchez System.Collections.Concurrent.ConcurrentDictionary<TKey, TValue> . Les nouvelles collections concurrentes utilisent considérablement amélioré les mécanismes de verrouillage et doivent effectuer excellectly dans les algorithmes parallèles.

Edit: Le résultat pourrait ressembler à ceci:

ConcurrentDictionary<T,K> cache = ...;
Parallel.ForEach(items, (item, loopState) =>
{
    K value;
    if (!cache.TryGetValue(item.Key, out value))
    {
        value = SomeIntensiveOperation();
        cache.TryAdd(item.Key, value);
    }

    // Do something with value
} );

Un mot d'avertissement: si les éléments items n'ont pas tous item.Key unique SomeIntensiveOperation pourrait alors obtenir appelé deux fois pour cette clé. Dans l'exemple, la clé ne sont pas transmises à SomeIntensiveOperation, mais cela signifie que le code « faire quelque chose avec la valeur » pourrait exécuter clé / ValueA et des paires de clés / valeurB, et un seul résultat obtiendrait stocké dans le cache (pas nécessairement la une première calculée par SomeIntensiveOperation non plus). Vous auriez besoin d'une usine paresseuse parallèle à gérer cette si c'est un problème. En outre, pour des raisons évidentes SomeIntensiveOperation devrait être thread-safe.

Autres conseils

Utilisez un ReaderWriterLock, ce qui a de bonnes performances pour le travail qui a beaucoup de lectures et quelques écritures qui sont de courte durée. Votre problème semble correspondre à cette spécification.

Toutes les opérations de lecture se déroulera rapidement et sans verrouillage, la seule fois que quelqu'un sera bloqué est quand une écriture se passe, et que écrire est aussi longtemps qu'il le faut pour fourrer quelque chose dans un Hashtable.

ReaderWriterLockSlim sur MSDN

Je suppose que je vais jeter en bas du code ...

ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
Hashtable myTable = new Hashtable();
Parallel.ForEach(items, (item, loopState) =>
{
    cacheLock.EnterReadLock();
    MyObject myObj = myTable.TryGet(item.Key);
    cacheLock.ExitReadLock();

    // If the object isn't cached, calculate it and cache it
    if(myObj == null)
    {
       myObj = SomeIntensiveOperation();
       cacheLock.EnterWriteLock();
       try
       {
           myTable.Add(item.Key, myObj);
       }
       finally
       {
           cacheLock.ExitWriteLock();
       }           
    }
    // Do something with myObj
    // some code here
}

static object TryGet(this Hashtable table, object key)
{
    if(table.Contains(key))
        return table[key]
    else
        return null;
}

Je ne vois pas d'autre choix correct que de serrures utiliser (plus ou moins explicite) (A Hashtable synchronisé remplace simplement toutes les méthodes avec des serrures).

Une autre option pourrait être de permettre le dictionnaire de sortir de la synchronisation. La condition de course le dictionnaire, il ne sera pas corrompu juste besoin du code pour faire des calculs superflus. Profil du code pour vérifier si le memoization de verrouillage ou manquant a des effets plus graves.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top