Monitor contra bloqueio

90

Quando é apropriado usar a Monitorclasse ou a lockpalavra - chave para segurança de thread em C #?

EDITAR: Parece pelas respostas até agora que locké um atalho para uma série de ligações para a Monitorclasse. Para que exatamente é a abreviação de chamada de bloqueio? Ou mais explicitamente,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Atualizar

Obrigado a todos por sua ajuda: Eu postei outra pergunta como um acompanhamento de algumas das informações que todos vocês forneceram. Como você parece ser bem versado nessa área, postei o link: O que há de errado com essa solução para bloquear e gerenciar exceções bloqueadas?

smartcaveman
fonte

Respostas:

90

Eric Lippert fala sobre isso em seu blog: Bloqueios e exceções não se misturam

O código equivalente difere entre C # 4.0 e versões anteriores.


Em C # 4.0, é:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Ele se baseia em Monitor.Enterdefinir atomicamente o sinalizador quando o bloqueio é executado.


E antes era:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Isso depende de nenhuma exceção sendo lançada entre Monitor.Entere o try. Acho que no código de depuração essa condição foi violada porque o compilador inseriu um NOP entre eles e, portanto, tornou possível o aborto de thread entre eles.

CodesInChaos
fonte
Como afirmei, o primeiro exemplo é C # 4 e o outro é o que as versões anteriores usam.
CodesInChaos
Como uma observação lateral, C # via CLR menciona uma advertência da palavra-chave de bloqueio: muitas vezes você pode querer fazer algo para restaurar o estado corrompido (se aplicável) antes de liberar o bloqueio. Uma vez que a palavra-chave lock não nos permite colocar coisas no bloco catch, devemos considerar escrever a versão longa try-catch-finally para rotinas não triviais.
kizzx2
5
IMO restaurar o estado compartilhado é ortogonal ao bloqueio / multi-threading. Portanto, deve ser feito com um try-catch / finally dentro do lockbloco.
CodesInChaos
2
@ kizzx2: Esse padrão seria especialmente bom com bloqueios de leitor-escritor. Se ocorrer uma exceção no código que contém um bloqueio de leitor, não há razão para esperar que o recurso protegido possa ser danificado e, portanto, não há razão para invalidá-lo. Se ocorrer uma exceção dentro de um bloqueio de gravador e o código de tratamento de exceção não indicar expressamente que o estado do objeto protegido foi reparado, isso sugeriria que o objeto pode estar danificado e deve ser invalidado. IMHO, exceções inesperadas não devem travar um programa, mas devem invalidar qualquer coisa que possa estar corrompida.
supercat
2
@ArsenZahray Você não precisa Pulsede um bloqueio simples. É importante em alguns cenários avançados de multithread. Nunca usei Pulsediretamente.
CodesInChaos
43

locké apenas um atalho para Monitor.Entercom try+ finallye Monitor.Exit. Use a instrução de bloqueio sempre que for suficiente - se você precisar de algo como TryEnter, terá que usar o Monitor.

Lukáš Novotný
fonte
23

Uma instrução de bloqueio é equivalente a:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

No entanto, lembre-se de que Monitor também pode Wait () e Pulse () , que geralmente são úteis em situações complexas de multithreading.

Atualizar

No entanto, em C # 4 é implementado de forma diferente:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Agradecimentos a CodeInChaos pelos comentários e links

Shekhar_Pro
fonte
No C # 4, a instrução de bloqueio é implementada de maneira diferente. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
16

Monitoré mais flexível. Meu caso de uso favorito de usar monitor é quando você não quer esperar pela sua vez e apenas pular :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
fonte
6

Como outros já disseram, locké "equivalente" a

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Mas só por curiosidade, lockpreservará a primeira referência que você passar para ele e não jogará se você alterar. Eu sei que não é recomendado mudar o objeto bloqueado e você não quer fazer isso.

Mas, novamente, para a ciência, isso funciona bem:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... E isso não:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Erro:

Uma exceção do tipo 'System.Threading.SynchronizationLockException' ocorreu em 70783sTUDIES.exe, mas não foi tratada no código do usuário

Informações adicionais: O método de sincronização de objetos foi chamado de um bloco de código não sincronizado.

Isso ocorre porque Monitor.Exit(lockObject);vai agir sobre o lockObjectque mudou porque stringssão imutáveis, então você está chamando de um bloco de código não sincronizado ... mas de qualquer maneira. Este é apenas um fato divertido.

André Pena
fonte
"Isso ocorre porque Monitor.Exit (lockObject); atuará em lockObject". Então o bloqueio não faz nada com o objeto? Como funciona o bloqueio?
Yugo Amaryl
@YugoAmaryl, suponho que seja porque a instrução de bloqueio mantém em mente a referência passada primeiro e depois a usa em vez de usar a referência alterada, como:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.
3

Ambos são a mesma coisa. lock é uma palavra-chave c sharp e usa a classe Monitor.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx

RobertoBr
fonte
3
Observe msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "Na verdade, a palavra-chave de bloqueio é implementada com a classe Monitor. Por exemplo"
RobertoBr
1
a implementação subjacente de bloqueio usa Monitor, mas eles não são a mesma coisa, considere os métodos fornecidos pelo monitor que não existem para bloqueio e a maneira como você pode bloquear e desbloquear em blocos de código separados.
eran otzap
3

O bloqueio e o comportamento básico do monitor (entrar + sair) são mais ou menos iguais, mas o monitor tem mais opções que permitem mais possibilidades de sincronização.

O bloqueio é um atalho e é a opção para o uso básico.

Se você precisar de mais controle, o monitor é a melhor opção. Você pode usar o Wait, TryEnter e o Pulse, para usos avançados (como barreiras, semáforos e assim por diante).

Borja
fonte
1

A palavra-chave Lock Lock garante que um thread esteja executando um trecho de código por vez.

lock (lockObject)

        {
        //   Body
        }

A palavra-chave lock marca um bloco de instrução como uma seção crítica, obtendo o bloqueio de exclusão mútua para um determinado objeto, executando uma instrução e liberando o bloqueio

Se outro thread tentar inserir um código bloqueado, ele aguardará, bloqueará, até que o objeto seja liberado.

Monitor O Monitor é uma classe estática e pertence ao namespace System.Threading.

Ele fornece bloqueio exclusivo no objeto para que apenas um thread possa entrar na seção crítica em qualquer ponto do tempo.

Diferença entre monitorar e bloquear em C #

O bloqueio é o atalho para Monitor.Enter com try e finalmente. As alças de bloqueio tentam e bloqueiam internamente .

Se você quiser mais controle para implementar soluções avançadas de multithreading usando TryEnter() Wait(), Pulse()e PulseAll()métodos, então a classe Monitor é a sua opção.

C # Monitor.wait(): um thread aguarda a notificação de outros threads.

Monitor.pulse(): Um tópico é notificado para outro tópico.

Monitor.pulseAll(): Um thread notifica todos os outros threads dentro de um processo

Aatrey
fonte
0

Além de todas as explicações acima, lock é uma instrução C # enquanto Monitor é uma classe de .NET localizada no namespace System.Threading.

PureSilence
fonte