Eu tenho a seguinte turma.
class Test{
public HashSet<string> Data = new HashSet<string>();
}
Preciso alterar o campo "Dados" de diferentes threads, portanto, gostaria de algumas opiniões sobre minha implementação atual segura de thread.
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 uma solução melhor para ir diretamente ao campo e protegê-lo do acesso simultâneo por vários threads?
System.Collections.Concurrent
ReaderWriterLock
será útil (eficiente) quando vários leitores e um único escritor. Temos de saber se este é o caso de OPRespostas:
Sua implementação está correta. Infelizmente, o .NET Framework não fornece um tipo de hashset simultâneo interno. No entanto, existem algumas soluções alternativas.
ConcurrentDictionary (recomendado)
Este primeiro é usar a classe
ConcurrentDictionary<TKey, TValue>
no espaço para nomeSystem.Collections.Concurrent
. No caso, o valor é inútil, portanto, podemos usar um simplesbyte
(1 byte na memória).Essa é a opção recomendada porque o tipo é seguro para threads e oferece as mesmas vantagens que um
HashSet<T>
chave e um valor exceto objetos diferentes.Fonte: Social MSDN
ConcurrentBag
Se você não se importa com as entradas duplicadas, pode usar a classe
ConcurrentBag<T>
no mesmo espaço para nome da classe anterior.Auto-implementação
Por fim, como você fez, você pode implementar seu próprio tipo de dados, usando o lock ou outras formas que o .NET fornece para você ser seguro para threads. Aqui está um ótimo exemplo: Como implementar o ConcurrentHashSet no .Net
A única desvantagem dessa solução é que o tipo
HashSet<T>
não tem acesso simultâneo oficial, mesmo para operações de leitura.Cito o código do post vinculado (originalmente escrito por Ben Mosher ).
EDIT: Mova os métodos de bloqueio de entrada para fora dos
try
blocos, pois eles podem lançar uma exceção e executar as instruções contidas nosfinally
blocos.fonte
null
referência (a referência precisa de 4 bytes em tempo de execução de 32 bits e 8 bytes em tempo de execução de 64 bits). Portanto, o uso de abyte
, uma estrutura vazia ou semelhante pode reduzir o espaço ocupado pela memória (ou não, se o tempo de execução alinhar os dados nos limites da memória nativa para um acesso mais rápido).Em vez de envolver
ConcurrentDictionary
ou bloquear umHashSet
, criei um real comConcurrentHashSet
base emConcurrentDictionary
.Esta implementação suporta operações básicas por item sem
HashSet
as operações definidas, pois fazem menos sentido nos cenários simultâneos IMO:Saída: 2
Você pode obtê-lo no NuGet aqui e ver a fonte no GitHub aqui .
fonte
ISet<T>
interface bo, na verdade, corresponde àHashSet<T>
semântica?Overlaps
por exemplo, seria necessário bloquear a instância durante toda a execução ou fornecer uma resposta que já pode estar errada. Ambas as opções são IMO ruins (e podem ser adicionadas externamente pelos consumidores).Como ninguém mais o mencionou, vou oferecer uma abordagem alternativa que pode ou não ser apropriada para seu objetivo específico:
Coleções imutáveis da Microsoft
De um post da equipe da MS por trás:
Essas coleções incluem ImmutableHashSet <T> e ImmutableList <T> .
atuação
Como as coleções imutáveis usam estruturas de dados em árvore abaixo para permitir o compartilhamento estrutural, suas características de desempenho são diferentes das coleções mutáveis. Ao comparar com uma coleção mutável de bloqueio, os resultados dependerão da contenção de bloqueio e dos padrões de acesso. No entanto, retirado de outra postagem no blog sobre as coleções imutáveis:
Em outras palavras, em muitos casos, a diferença não será perceptível e você deve optar por uma opção mais simples - que para conjuntos simultâneos seria usada
ImmutableHashSet<T>
, já que você não possui uma implementação mutável de bloqueio! :-)fonte
ImmutableHashSet<T>
não ajuda muito se sua intenção é atualizar o estado compartilhado de vários threads ou estou faltando alguma coisa aqui?ImmutableInterlocked.Update
parece ser o elo que faltava. Obrigado!A parte complicada de fazer uma
ISet<T>
concorrente é que os métodos definidos (união, interseção, diferença) são de natureza iterativa. No mínimo, você precisa iterar todos os n membros de um dos conjuntos envolvidos na operação, enquanto bloqueia os dois conjuntos.Você perde as vantagens de
ConcurrentDictionary<T,byte>
quando precisa bloquear todo o conjunto durante a iteração. Sem travamento, essas operações não são seguras para threads.Dada a sobrecarga adicional de
ConcurrentDictionary<T,byte>
, provavelmente é mais sensato usar o peso mais leveHashSet<T>
e envolver tudo em travas.Se você não precisar das operações definidas, use
ConcurrentDictionary<T,byte>
e use apenasdefault(byte)
como o valor ao adicionar chaves.fonte
Eu prefiro soluções completas, então fiz isso: lembre-se de que meu Count é implementado de uma maneira diferente, porque não vejo por que alguém deveria ser proibido de ler o hashset enquanto tentava contar seus valores.
@ Zen, Obrigado por começar.
fonte
EnterWriteLock
, por queEnterReadLock
existe? O bloqueio de leitura não pode ser usado para métodos comoContains
?