J'ai la classe suivante.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
J'ai besoin de changer le champ "Données" de différents threads, donc j'aimerais avoir des opinions sur mon implémentation thread-safe actuelle.
class Test{
public HashSet<string> Data = new HashSet<string>();
public void Add(string Val){
lock(Data) Data.Add(Val);
}
public void Remove(string Val){
lock(Data) Data.Remove(Val);
}
}
Existe-t-il une meilleure solution, pour aller directement sur le terrain et le protéger de l'accès simultané par plusieurs threads?
System.Collections.Concurrent
ReaderWriterLock
sera utile (efficace) lorsque plusieurs lecteurs et un seul écrivain. Nous devons savoir si c'est le cas pour OPRéponses:
Votre implémentation est correcte. Le .NET Framework ne fournit malheureusement pas de type de hachage simultané intégré. Cependant, il existe quelques solutions de contournement.
ConcurrentDictionary (recommandé)
Cette première consiste à utiliser la classe
ConcurrentDictionary<TKey, TValue>
dans l'espace de nomsSystem.Collections.Concurrent
. Dans le cas, la valeur est inutile, on peut donc utiliser un simplebyte
(1 octet en mémoire).C'est l'option recommandée car le type est thread-safe et vous offre les mêmes avantages qu'une
HashSet<T>
clé et une valeur sauf sont des objets différents.Source: MSDN social
ConcurrentBag
Si les entrées en double ne vous dérangent pas, vous pouvez utiliser la classe
ConcurrentBag<T>
dans le même espace de noms que la classe précédente.Auto-implémentation
Enfin, comme vous l'avez fait, vous pouvez implémenter votre propre type de données, en utilisant le verrouillage ou d'autres moyens que le .NET vous offre pour être thread-safe. Voici un excellent exemple: Comment implémenter ConcurrentHashSet dans .Net
Le seul inconvénient de cette solution est que le type
HashSet<T>
n'a pas officiellement d'accès simultané, même pour les opérations de lecture.Je cite le code de l'article lié (écrit à l'origine par Ben Mosher ).
EDIT: Déplacez les méthodes de verrouillage d'entrée à l'extérieur des
try
blocs, car elles pourraient lever une exception et exécuter les instructions contenues dans lesfinally
blocs.la source
null
référence (la référence nécessite 4 octets dans un runtime 32 bits et 8 octets dans un runtime 64 bits). Par conséquent, l'utilisation d'unbyte
, d'une structure vide ou similaire peut réduire l'encombrement de la mémoire (ou pas si le runtime aligne les données sur les limites de la mémoire native pour un accès plus rapide).Au lieu d'envelopper un
ConcurrentDictionary
ou de verrouiller un,HashSet
j'ai créé un réelConcurrentHashSet
basé surConcurrentDictionary
.Cette implémentation prend en charge les opérations de base par article sans
HashSet
les opérations d'ensemble car elles ont moins de sens dans les scénarios simultanés IMO:Sortie: 2
Vous pouvez l'obtenir à partir de NuGet ici et voir la source sur GitHub ici .
la source
ISet<T>
interface pour correspondre à laHashSet<T>
sémantique?Overlaps
par exemple, il faudrait soit verrouiller l'instance tout au long de son exécution, soit fournir une réponse qui peut déjà être erronée. Les deux options sont mauvaises IMO (et peuvent être ajoutées en externe par les consommateurs).Étant donné que personne d'autre ne l'a mentionné, je proposerai une approche alternative qui peut ou non être appropriée à votre objectif particulier:
Collections immuables Microsoft
À partir d'un article de blog de l'équipe MS derrière:
Ces collections incluent ImmutableHashSet <T> et ImmutableList <T> .
Performance
Étant donné que les collections immuables utilisent des structures de données arborescentes en dessous pour permettre le partage structurel, leurs caractéristiques de performance sont différentes de celles des collections mutables. Lors d'une comparaison avec une collection mutable verrouillable, les résultats dépendront du conflit de verrouillage et des modèles d'accès. Cependant, tiré d' un autre article de blog sur les collections immuables:
En d'autres termes, dans de nombreux cas, la différence ne sera pas perceptible et vous devriez opter pour le choix le plus simple - qui serait à utiliser pour les ensembles simultanés
ImmutableHashSet<T>
, car vous n'avez pas d'implémentation de verrouillage mutable existante! :-)la source
ImmutableHashSet<T>
n'aide pas beaucoup si votre intention est de mettre à jour l'état partagé à partir de plusieurs threads ou est-ce que je manque quelque chose ici?ImmutableInterlocked.Update
semble être le chaînon manquant. Je vous remercie!La partie délicate de la création d'un
ISet<T>
concurrent est que les méthodes d'ensemble (union, intersection, différence) sont de nature itérative. Au minimum, vous devez parcourir les n membres de l'un des ensembles impliqués dans l'opération, tout en verrouillant les deux ensembles.Vous perdez les avantages d'un
ConcurrentDictionary<T,byte>
lorsque vous devez verrouiller l'ensemble entier pendant l'itération. Sans verrouillage, ces opérations ne sont pas thread-safe.Compte tenu des frais généraux supplémentaires
ConcurrentDictionary<T,byte>
, il est probablement plus sage d'utiliser le poids plus légerHashSet<T>
et de tout entourer de serrures.Si vous n'avez pas besoin des opérations définies, utilisez
ConcurrentDictionary<T,byte>
et utilisez simplementdefault(byte)
comme valeur lorsque vous ajoutez des clés.la source
Je préfère les solutions complètes, alors j'ai fait ceci: Attention, mon compte est implémenté d'une manière différente car je ne vois pas pourquoi on devrait interdire de lire le hashset en essayant de compter ses valeurs.
@Zen, merci d'avoir démarré.
la source
EnterWriteLock
, pourquoiEnterReadLock
existe-t- il même? Le verrou de lecture ne peut-il pas être utilisé pour des méthodes commeContains
?