Não consegui encontrar informações suficientes sobre os ConcurrentDictionary
tipos, então pensei em perguntar sobre isso aqui.
Atualmente, eu uso a Dictionary
para manter todos os usuários que são acessados constantemente por vários threads (de um pool de threads, portanto, não há quantidade exata de threads), e ele tem acesso sincronizado.
Recentemente, descobri que havia um conjunto de coleções seguras para threads no .NET 4.0 e parece muito agradável. Fiquei me perguntando, qual seria a opção 'mais eficiente e mais fácil de gerenciar', pois tenho a opção entre ter um Dictionary
acesso normal com sincronizado ou um ConcurrentDictionary
que já seja seguro para threads.
Respostas:
Uma coleção segura para threads vs. uma coleção não segura para threads pode ser vista de uma maneira diferente.
Considere uma loja sem funcionário, exceto no check-out. Você tem muitos problemas se as pessoas não agirem com responsabilidade. Por exemplo, digamos que um cliente pegue uma lata de uma pirâmide - enquanto um funcionário está construindo a pirâmide, todo o inferno se abriria. Ou, se dois clientes alcançarem o mesmo item ao mesmo tempo, quem ganha? Haverá uma briga? Esta é uma coleção não segura para threads. Existem várias maneiras de evitar problemas, mas todas elas exigem algum tipo de bloqueio ou acesso explícito de uma maneira ou de outra.
Por outro lado, considere uma loja com um funcionário em uma mesa e você só pode fazer compras através dele. Você entra na fila e pede um item para ele, ele o devolve e você sai da fila. Se você precisar de vários itens, poderá coletar o máximo de itens em cada viagem de ida e volta, mas lembre-se de tomar cuidado, para não incomodar o atendente, pois isso irritará os outros clientes na fila atrás de você.
Agora considere isso. Na loja com um funcionário, e se você chegar até a frente da fila e perguntar ao funcionário "Você tem algum papel higiênico", e ele disser "Sim", e você disser "Ok, eu ' Entrarei em contato com você quando souber o quanto preciso "e, quando voltar à frente da fila, a loja poderá estar esgotada. Este cenário não é impedido por uma coleção threadsafe.
Uma coleção threadsafe garante que suas estruturas de dados internas sejam válidas o tempo todo, mesmo se acessadas de vários threads.
Uma coleção não segura para threads não possui essas garantias. Por exemplo, se você adicionar algo a uma árvore binária em um thread, enquanto outro estiver ocupado reequilibrando a árvore, não há garantia de que o item será adicionado ou mesmo que a árvore ainda seja válida depois, pode estar corrompida além da esperança.
Uma coleção threadsafe, no entanto, não garante que as operações seqüenciais no thread funcionem no mesmo "instantâneo" de sua estrutura de dados interna, o que significa que, se você tiver um código como este:
você pode obter uma NullReferenceException porque, entre
tree.Count
etree.First()
, outro encadeamento limpou os nós restantes na árvore, o que significaFirst()
que retornaránull
.Nesse cenário, é necessário verificar se a coleção em questão possui uma maneira segura de obter o que deseja, talvez seja necessário reescrever o código acima ou talvez seja necessário bloquear.
fonte
Dictionary
e lidar com o bloqueio por conta própria versus oConcurrentDictionary
tipo que está embutido no .NET 4+. Na verdade, estou um pouco confuso que isso tenha sido aceito.Você ainda precisa ter muito cuidado ao usar coleções seguras para encadeamento, porque não é possível ignorar todos os problemas de encadeamento. Quando uma coleção se anuncia como segura para threads, geralmente significa que permanece em um estado consistente, mesmo quando vários threads estão lendo e gravando simultaneamente. Mas isso não significa que um único encadeamento verá uma sequência "lógica" de resultados se chamar vários métodos.
Por exemplo, se você primeiro verificar se existe uma chave e depois obter o valor que corresponde à chave, essa chave poderá não existir mais, mesmo com uma versão ConcurrentDictionary (porque outro encadeamento poderia ter removido a chave). Você ainda precisa usar o bloqueio nesse caso (ou melhor: combine as duas chamadas usando o TryGetValue ).
Portanto, use-os, mas não pense que isso lhe dá um passe livre para ignorar todos os problemas de simultaneidade. Você ainda precisa ter cuidado.
fonte
TryGetValue
sempre fez parteDictionary
e sempre foi a abordagem recomendada. Os importantes métodos "simultâneos" queConcurrentDictionary
introduz sãoAddOrUpdate
eGetOrAdd
. Então, boa resposta, mas poderia ter escolhido um exemplo melhor.Internamente, o ConcurrentDictionary usa um bloqueio separado para cada bloco de hash. Desde que você use apenas Add / TryGetValue e métodos semelhantes que funcionem em entradas únicas, o dicionário funcionará como uma estrutura de dados quase sem bloqueios, com os respectivos benefícios de desempenho. OTOH, os métodos de enumeração (incluindo a propriedade Count) bloqueiam todos os buckets de uma só vez e, portanto, são piores que um Dicionário sincronizado, em termos de desempenho.
Eu diria, basta usar ConcurrentDictionary.
fonte
Eu acho que o método ConcurrentDictionary.GetOrAdd é exatamente o que a maioria dos cenários com vários threads precisa.
fonte
Você já viu as extensões reativas para .Net 3.5sp1. Segundo Jon Skeet, eles fizeram o backport de um pacote de extensões paralelas e estruturas de dados simultâneas para .Net3.5 sp1.
Há um conjunto de exemplos para o .Net 4 Beta 2, que descreve com bastante detalhes sobre como usá-los nas extensões paralelas.
Acabei de passar a última semana testando o ConcurrentDictionary usando 32 threads para executar E / S. Parece funcionar como anunciado, o que indicaria uma enorme quantidade de testes realizados.
Edit : .NET 4 ConcurrentDictionary e padrões.
A Microsoft lançou um pdf chamado Patterns of Paralell Programming. Realmente vale a pena fazer o download, como descrito em detalhes muito bons, os padrões certos a serem usados nas extensões concorrentes .Net 4 e os anti padrões a serem evitados. Aqui está.
fonte
Basicamente, você quer ir com o novo ConcurrentDictionary. Logo de cara, você precisa escrever menos código para criar programas seguros para threads.
fonte
A
ConcurrentDictionary
é uma ótima opção se cumprir todas as suas necessidades thread-segurança. Se não estiver, em outras palavras, você está fazendo algo levemente complexo, umDictionary
+ normallock
pode ser uma opção melhor. Por exemplo, digamos que você esteja adicionando alguns pedidos a um dicionário e deseje manter atualizado o valor total dos pedidos. Você pode escrever um código como este:Este código não é seguro para threads. Vários segmentos que atualizam o
_totalAmount
campo podem deixá-lo em um estado corrompido. Portanto, você pode tentar protegê-lo com umlock
:Este código é "mais seguro", mas ainda não é seguro para threads. Não há garantia de que isso
_totalAmount
seja consistente com as entradas no dicionário. Outro segmento pode tentar ler esses valores, para atualizar um elemento da interface do usuário:A
totalAmount
podem não corresponder à contagem de pedidos no dicionário. As estatísticas exibidas podem estar erradas. Neste ponto, você perceberá que deve estender alock
proteção para incluir a atualização do dicionário:Esse código é perfeitamente seguro, mas todos os benefícios do uso de a
ConcurrentDictionary
se foram.Dictionary
, porque o bloqueio interno dentro doConcurrentDictionary
agora é desperdício e redundante.TryAdd
?,AddOrUpdate
?).Portanto, meu conselho é: comece com um
Dictionary
+lock
e mantenha a opção de atualizar posteriormente para aConcurrentDictionary
como uma otimização de desempenho, se essa opção for realmente viável. Em muitos casos, não será.fonte
Usamos o ConcurrentDictionary para coleção em cache, que é preenchida novamente a cada 1 hora e, em seguida, lida por vários threads do cliente, semelhante à solução para a thread Este exemplo de exemplo é seguro? questão.
Descobrimos que alterá-lo para ReadOnlyDictionary melhorou o desempenho geral.
fonte