Como o bloqueio funciona exatamente?

527

Vejo que, ao usar objetos que não são seguros para threads, envolvemos o código com um bloqueio como este:

private static readonly Object obj = new Object();

lock (obj)
{
    // thread unsafe code
}

Então, o que acontece quando vários threads acessam o mesmo código (vamos supor que ele esteja sendo executado em um aplicativo Web ASP.NET). Eles estão na fila? Se sim, quanto tempo eles vão esperar?

Qual é o impacto no desempenho devido ao uso de bloqueios?

NLV
fonte
1
^ link morto, veja: jonskeet.uk/csharp/threads/index.html
Ivan Pavičić

Respostas:

448

A lockdeclaração é traduzida pelo C # 3.0 para o seguinte:

var temp = obj;

Monitor.Enter(temp);

try
{
    // body
}
finally
{
    Monitor.Exit(temp);
}

No C # 4.0, isso foi alterado e agora é gerado da seguinte maneira:

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

Você pode encontrar mais informações sobre o que Monitor.Enterfaz aqui . Para citar o MSDN:

Use Enterpara adquirir o Monitor no objeto passado como parâmetro. Se outro segmento executou um Enter no objeto, mas ainda não executou o correspondente Exit, o segmento atual será bloqueado até que o outro segmento libere o objeto. É legal que o mesmo encadeamento invoque Entermais de uma vez sem bloqueá-lo; no entanto, um número igual de Exitchamadas deve ser chamado antes que outros threads que aguardam no objeto sejam desbloqueados.

O Monitor.Entermétodo irá esperar infinitamente; ele vai não expirar.

Steven
fonte
15
De acordo com o MSDN "Geralmente, é preferível usar as palavras-chave lock (C #) ou SyncLock (Visual Basic) em vez de usar diretamente a classe Monitor, porque o lock ou SyncLock é mais conciso e porque o lock ou SyncLock garante que o monitor subjacente seja liberado, mesmo se o código protegido lança uma exceção. Isso é feito com a palavra-chave finally, que executa seu bloco de código associado, independentemente de uma exceção ser lançada. " msdn.microsoft.com/pt-br/library/ms173179.aspx
Aiden Strydom
10
Qual é o objetivo da var temp = obj; linha. já que é apenas um árbitro, para que serve um outro?
priehl
11
@priehl Permite que o usuário mude objsem que todo o sistema entre em conflito.
Steven
7
@Joymon eventualmente, todo recurso de idioma é açúcar sintático. Os recursos de idioma são sobre tornar os desenvolvedores mais produtivos e tornar os aplicativos mais fáceis de manter, assim como o recurso de bloqueio.
Steven
2
Corrigir. Esse é o objetivo completo da lockinstrução e do Monitor: para que você possa executar uma operação em um encadeamento sem precisar se preocupar com outro encadeamento.
Dizzy H. Muffin
285

É mais simples do que você pensa.

De acordo com a Microsoft : A lockpalavra-chave garante que um segmento não entre em uma seção crítica do código enquanto outro segmento está na seção crítica. Se outro encadeamento tentar inserir um código bloqueado, ele aguardará, bloqueado, até que o objeto seja liberado.

A lockpalavra-chave chama Enterno início do bloco e Exitno final do bloco. lockpalavra-chave realmente lida com a Monitorclasse no back-end.

Por exemplo:

private static readonly Object obj = new Object();

lock (obj)
{
    // critical section
}

No código acima, primeiro o thread entra em uma seção crítica e, em seguida, será bloqueado obj. Quando outro thread tenta entrar, ele também tenta bloquear obj, que já está bloqueado pelo primeiro thread. O segundo thread precisará aguardar o lançamento do primeiro thread obj. Quando o primeiro thread sair, outro será bloqueado obje entrará na seção crítica.

Umar Abbas
fonte
9
devemos criar um objeto fictício para bloquear ou podemos bloquear uma variável existente no contexto?
batmaci
9
@batmaci - Bloquear um objeto fictício privado separado garante a você que ninguém mais está bloqueando esse objeto. Se você bloquear os dados e esse mesmo dado estiver visível para o exterior, você perderá essa garantia.
amigos estão
8
O que acontece se mais de um processo estiver aguardando a liberação do bloqueio? Os processos em espera estão na fila para bloquear a seção crítica na ordem FIFO?
jstuardo
@jstuardo - Eles estão na fila, mas não é garantido que o pedido seja FIFO. Confira este link: albahari.com/threading/part2.aspx
Umar Abbas
Copiado sem atribuição de net-informations.com/faq/qk/lock.htm
Martijn Pieters
47

Não, eles não estão na fila, estão dormindo

Uma declaração de bloqueio do formulário

lock (x) ... 

onde x é uma expressão de um tipo de referência, é precisamente equivalente a

var temp = x;
System.Threading.Monitor.Enter(temp); 
try { ... } 
finally { System.Threading.Monitor.Exit(temp); }

Você só precisa saber que eles estão esperando um pelo outro, e apenas um segmento entrará para bloquear o bloco, os outros esperarão ...

O monitor está totalmente escrito em .net, por isso é rápido o suficiente, veja também a classe Monitor com refletor para obter mais detalhes

Arsen Mkrtchyan
fonte
6
Observe que o código emitido para a lockinstrução mudou ligeiramente em C # 4: blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
LukeH
@ArsenMkrt, eles não são mantidos em "Bloqueado" estado "Queue ?. Eu acho que há alguma diferença entre o sono eo estado de bloco, não é?
Mohanavel
que diferença você quer dizer @Mohanavel?
Arsen Mkrtchyan
1
Essa não era a questão. A pergunta era sobre a palavra-chave "bloquear". Suponha que um processo entre na seção "bloqueio". Isso significa que o processo bloqueia esse trecho de código e nenhum outro processo poderá entrar nessa seção até que esse bloqueio seja liberado. Bem .... agora, mais 2 processos tentam inserir o mesmo bloco. Como é protegido pela palavra-chave "lock", eles aguardam, de acordo com o que foi dito neste fórum. Quando o primeiro processo libera o bloqueio. Qual processo entra no bloco? o primeiro que tentou entrar ou o último?
jstuardo
1
Eu acho que você fio média em vez de processo ... se assim, que a resposta é não, não há garantia de que ninguém vai entrar ... mais aqui stackoverflow.com/questions/4228864/...
Arsen Mkrtchyan
29

Bloqueios impedem que outros threads executem o código contido no bloco de bloqueio. As linhas terão que esperar até que a linha dentro do bloco de bloqueio seja concluída e a trava seja liberada. Isso tem um impacto negativo no desempenho em um ambiente multithread. Se você precisar fazer isso, verifique se o código no bloco de bloqueio pode processar muito rapidamente. Você deve tentar evitar atividades caras, como acessar um banco de dados, etc.

Andrew
fonte
11

O impacto no desempenho depende da maneira como você bloqueia. Você pode encontrar uma boa lista de otimizações aqui: http://www.thinkingparallel.com/2007/07/31/10-ways-to-reduce-lock-contention-in-threaded-programs/

Basicamente, você deve tentar bloquear o mínimo possível, pois coloca seu código em espera no modo de suspensão. Se você tiver alguns cálculos pesados ​​ou código duradouro (por exemplo, upload de arquivo) em um bloqueio, isso resultará em uma enorme perda de desempenho.

Simon Woker
fonte
1
Mas tentar escrever código de bloqueio baixo geralmente pode resultar em erros sutis, difíceis de encontrar e corrigir, mesmo se você for um especialista na área. Usar uma fechadura costuma ser o menor dos dois males. Você deve travar exatamente o necessário, nem mais, nem menos!
LukeH 17/05
1
@LukeH: Existem alguns padrões de uso em que o código de bloqueio baixo pode ser muito simples e fácil [ do { oldValue = thing; newValue = updated(oldValue); } while (CompareExchange(ref thing, newValue, oldValue) != oldValue]. O maior perigo é que, se os requisitos evoluírem além do que essas técnicas podem lidar, pode ser difícil adaptar o código para lidar com isso.
Supercat 13/11
Link quebrado.
CarenRose 31/01/19
8

A parte na instrução lock pode ser executada apenas por um thread, portanto, todos os outros threads aguardarão indefinidamente que o thread que segura a trava termine. Isso pode resultar em um chamado deadlock.

Mr47
fonte
8

A lockinstrução é traduzida para chamadas para os métodos Entere Exitde Monitor.

A lockinstrução aguardará indefinidamente o lançamento do objeto de bloqueio.

Paolo Tedesco
fonte
4

O bloqueio está realmente oculto na classe Monitor .

Eufórico
fonte