Alguém pode explicar a diferença entre:
- lock (someobject) {}
- Usando o Mutex
- Usando o Semáforo
- Usando o Monitor
- Usando outras classes de sincronização .Net
Eu simplesmente não consigo entender. Parece-me que os dois primeiros são iguais?
Alguém pode explicar a diferença entre:
Eu simplesmente não consigo entender. Parece-me que os dois primeiros são iguais?
Respostas:
Ótima pergunta. Talvez eu esteja errado .. Deixe-me tentar .. Revisão 2 da minha resposta original .. com um pouco mais de compreensão. Obrigado por me fazer ler :)
bloqueio (obj)
Monitores
O uso de um bloqueio ou monitor é útil para impedir a execução simultânea de blocos de código sensíveis ao encadeamento, mas essas construções não permitem que um encadeamento comunique um evento a outro. Isso requer eventos de sincronização , que são objetos que possuem um dos dois estados, sinalizados e não sinalizados, que podem ser usados para ativar e suspender encadeamentos. Mutex, semáforos são conceitos no nível do SO. por exemplo, com um mutex nomeado, você pode sincronizar vários exes (gerenciados) (garantindo que apenas uma instância do seu aplicativo esteja em execução na máquina).
Mutex:
Semáforos (machucam meu cérebro).
Monitor
não permite a comunicação incorreta; você ainda pode Pulse
etc com umMonitor
Re "Usando outras classes de sincronização .Net" - algumas das outras que você deve conhecer:
Também há mais construções de bloqueio (baixa sobrecarga) no CCR / TPL (o Parallel Extensions CTP) - mas no IIRC, elas serão disponibilizadas no .NET 4.0
Conforme declarado na ECMA, e como você pode observar nos métodos Refletidos, a instrução lock é basicamente equivalente a
object obj = x;
System.Threading.Monitor.Enter(obj);
try {
…
}
finally {
System.Threading.Monitor.Exit(obj);
}
A partir do exemplo mencionado, vemos que os monitores podem bloquear objetos.
Os mutexe são úteis quando você precisa de sincronização entre processos, pois podem bloquear um identificador de string. O mesmo identificador de cadeia pode ser usado por diferentes processos para adquirir o bloqueio.
Os semáforos são como mutexes em esteróides, eles permitem acesso simultâneo, fornecendo uma contagem máxima de acesso simultâneo '. Quando o limite é atingido, o semáforo começa a bloquear qualquer acesso adicional ao recurso até que um dos chamadores libere o semáforo.
Fiz as aulas e o suporte ao CLR para segmentação no DotGNU e tenho alguns pensamentos ...
A menos que você precise de bloqueios entre processos, você sempre deve evitar o uso de Mutex e Semáforos. Essas classes no .NET são invólucros em torno do Win32 Mutex e Semáforos e são bastante pesados (exigem uma troca de contexto no Kernel que é cara - especialmente se o seu bloqueio não estiver em disputa).
Como outros mencionados, a instrução de bloqueio C # é mágica do compilador para Monitor.Enter e Monitor.Exit (existente em uma tentativa / finalmente).
Os monitores têm um mecanismo de sinal / espera simples, mas poderoso, que os Mutexes não possuem através dos métodos Monitor.Pulse / Monitor.Wait. O equivalente ao Win32 seria objetos de evento via CreateEvent que também existem no .NET como WaitHandles. O modelo Pulse / Wait é semelhante ao pthread_signal e ao pthread_wait do Unix, mas é mais rápido porque pode ser uma operação inteiramente no modo usuário no caso não contido.
Monitor.Pulse / Wait é simples de usar. Em um thread, bloqueamos um objeto, verificamos um sinalizador / estado / propriedade e, se não é o que esperamos, chamamos Monitor.Wait, que liberará o bloqueio e esperará até que um pulso seja enviado. Quando a espera retorna, retornamos e verificamos a flag / state / property novamente. No outro segmento, bloqueamos o objeto sempre que alteramos o sinalizador / estado / propriedade e, em seguida, chamamos PulseAll para ativar os segmentos de escuta.
Muitas vezes, queremos que nossas classes sejam seguras para threads, por isso colocamos bloqueios em nosso código. No entanto, geralmente é o caso de nossa classe ser usada apenas por um thread. Isso significa que os bloqueios abrandam desnecessariamente nosso código ... é aqui que otimizações inteligentes no CLR podem ajudar a melhorar o desempenho.
Não tenho certeza sobre a implementação de bloqueios da Microsoft, mas no DotGNU e Mono, um sinalizador de estado de bloqueio é armazenado no cabeçalho de cada objeto. Todo objeto no .NET (e Java) pode se tornar um bloqueio, portanto, todo objeto precisa oferecer suporte a isso em seus cabeçalhos. Na implementação do DotGNU, há um sinalizador que permite que você use uma hashtable global para cada objeto usado como um bloqueio - isso tem o benefício de eliminar uma sobrecarga de 4 bytes para cada objeto. Isso não é bom para a memória (especialmente para sistemas embarcados que não são muito encadeados), mas afeta o desempenho.
O Mono e o DotGNU usam efetivamente mutexes para executar o bloqueio / espera, mas usam operações de comparação e troca no estilo spinlock para eliminar a necessidade de realmente executar bloqueios, a menos que seja realmente necessário:
Você pode ver um exemplo de como os monitores podem ser implementados aqui:
http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup
Uma ressalva adicional para bloquear qualquer Mutex compartilhado que você identificou com um ID de string é que ele será padronizado como um mutex "Local \" e não será compartilhado entre as sessões em um ambiente de servidor de terminal.
Prefixe seu identificador de seqüência de caracteres com "Global \" para garantir que o acesso aos recursos compartilhados do sistema seja controlado adequadamente. Eu estava com um monte de problemas ao sincronizar as comunicações com um serviço executado na conta SYSTEM antes de perceber isso.
Eu tentaria evitar "lock ()", "Mutex" e "Monitor" se você puder ...
Confira o novo System.Collections.Concurrent namespace no .NET 4
Tem algumas classes de coleção thread-safe agradáveis
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
ConcurrentDictionary rocks! não há mais bloqueio manual para mim!
Na maioria dos casos, você não deve usar bloqueios (= monitores) ou mutexes / semáforos. Todos eles bloqueiam o encadeamento atual.
E você definitivamente não deve usar System.Collections.Concurrent
classes - elas são a principal fonte de condições de corrida, porque não suportam transações entre várias coleções e também bloqueiam o encadeamento atual.
Surpreendentemente, o .NET não possui mecanismos eficazes de sincronização.
Eu implementei fila serial do GCD ( Objc/Swift
mundo) em C # - muito leve, sem bloquear a ferramenta de sincronização que usa o pool de threads, com testes.
É a melhor maneira de sincronizar qualquer coisa na maioria dos casos - do acesso ao banco de dados (hello sqlite) à lógica de negócios.