Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Em nosso último artigo, discutimos as melhores práticas ao utilizar chamadas assíncronas. Hoje veremos diretrizes para bloqueio e sincronização, que fornecem um mecanismo para conceder acesso exclusivo a dados ou códigos, a fim de evitar a execução simultânea.
Determinar que você precisa de sincronização
Antes de considerar as opções de sincronização, você deve pensar em outras abordagens que evitam a necessidade de sincronização, como o acoplamento flexível. Particularmente, você precisa sincronizar quando vários usuários precisam acessar ou atualizar um recurso compartilhado ao mesmo tempo, tais como dados estáticos.
Determinar a abordagem
O CLR fornece vários mecanismos para o bloqueio e sincronização. Você deve considerar o que é certo para seu cenário específico:
- Opção enumeração MethodImplOptions.Synchronized - Isso fornece a capacidade de conceder acesso exclusivo a um método inteiro, o que raramente é uma boa ideia;
- Classe Interlocked - Isso proporciona aumento atômico e diminuição dos métodos para tipos. O Interlocked pode ser usado com os tipos de valor. Ele também suporta a capacidade de substituir um valor com base em uma comparação;
Determinar o escopo da sua abordagem
Você pode bloquear objetos diferentes e em diferentes níveis de granularidade, que vão até o tipo de linhas específicas de código dentro de um método individual. Identifique quais bloqueadores você tem e onde você adquire e libere-os. Você pode implementar uma política onde você sempre bloqueia, para fornecer um mecanismo de sincronização:
- Tipo - Tente evitar o bloqueio de um tipo (por exemplo. lock (typeof (tipo)). Esses obetos podem ser compartilhados entre domínios de aplicativos. Bloqueando o tipo, bloqueia todas as instâncias do tipo em o aplicativo domina em um processo. Isso pode levar a mau desempenho;
- "This" - Evite o bloqueio de objetos visíveis externamente (por exemplo. lock (this)), porque você não pode ter certeza se outro código está adquirindo o mesmo bloqueio e com que propósito ou política;
Enquanto faz o bloqueio, você também deve considerar a granularidade de seus bloqueios. As opções são como as seguintes:
Diretrizes de bloqueio e sincronização
Esta seção descreve as melhores práticas no desenvolvimento de código multithreaded que exige bloqueios e sincronização.
Adquira bloqueios tardios e libere-os mais cedo
Minimize o tempo que você espera e bloqueie recursos, porque a maioria dos recursos tende a ser compartilhado e limitado. Quanto mais rápido você liberar um recurso, mais cedo se torna disponível para outros segmentos. Adquira um bloqueio no recurso pouco antes de você precisar acessá-lo e libere-o imediatamente após usá-lo.
Evite o bloqueio e a sincronização, a menos quando for exigido
Sincronização requer processamento extra pelo CLR para conceder acesso exclusivo aos recursos. Se você não tem acesso aos dados de vários segmentos ou sincronização dos threads exigidos, não os implemente. Considere as seguintes opções antes de optar por um projeto ou execução que requer sincronização:
- Código de design que usa mecanismos de sincronização existentes, por exemplo, o objeto de cache usado por aplicativos ASP.NET;
- Código de design que evita modificações simultâneas nos dados. Implementação de sincronização pobre pode anular os efeitos da concorrência no seu aplicativo. Identificar áreas de código em seu aplicativo que podem ser reescritas para eliminar uma potencial modificação nos dados;
Use bloqueios granulares para reduzir a contenção
Quando usado corretamente e no nível apropriado de granularidade, bloqueios oferecen maior concorrência reduzindo a contenção. Considere as várias opções descritas anteriormente antes de decidir sobre o alcance do bloqueio. A abordagem mais eficiente é bloquear em um objeto e o escopo da duração do bloqueio para as linhas apropriadas de código que acessa um recurso compartilhado.
Evite o excesso de bloqueios refinados
Bloqueios refinados protegem uma pequena quantidade de dados, ou uma pequena quantidade de código. Quando usado corretamente, proporcionam concorrência superior, reduzindo a contenção de bloqueio. Usados de forma inadequada, eles podem adicionar complexidade e diminuir o desempenho e simultaneidade. Evite usar vários bloqueios refinados dentro de seu código. O código a seguir mostra um exemplo de instruções de bloqueio múltiplo usadas para controlar três recursos.
s = new Singleton();
sb1 = new StringBuilder();
sb2 = new StringBuilder();
s.IncDoubleWrite(sb1, sb2)
class Singleton
{
private static Object myLock = new Object();
private int count;
Singleton()
{
count = 0;
}
public void IncDoubleWrite(StringBuilder sb1, StringBuilder sb2)
{
lock (myLock)
{
count++;
sb1.AppendFormat(?Foo {0}?, count);
sb2.AppendFormat(?Bar {0}?, count);
}
}
public void DecDoubleWrite(StringBuilder sb1, StringBuilder sb2)
{
lock (myLock)
{
count?;
sb1.AppendFormat(?Foo {0}?, count);
sb2.AppendFormat(?Bar {0}?, count);
}
}
}
Evite fazer do thread safety um padrão para o seu tipo
Considere as seguintes diretrizes ao decidir por thread safety como uma opção para os seus tipos:
Use o bloqueio refinado Statement (C #) ao invés do sincronizado
O atributo MethodImplOptions.Synchronized irá garantir que apenas um thread seja executado em qualquer parte do método atribuído, a qualquer momento. No entanto, se você tiver métodos longos que travam poucos recursos, considere usar a instrução de bloqueio ao invés de usar a opção de sincronizar; assim, você encurtar a duração do seu bloqueio e melhorar a concorrência.
[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void MyMethod ()
//use of lock
public void MyMethod()
{
? lock(mylock)
{
// code here may assume it is the only code that has acquired mylock
// and use resources accordingly
? }
}
Evite o bloqueio "this"
Evite o bloqueio "this" em sua classe por razões de correção, não para qualquer ganho de desempenho específico. Para evitar esse problema, considere as seguintes soluções:
Provide a private object to lock on.
public class A {
? lock(this) { ? }
?}
// Change to the code below:
public class A
{
private Object thisLock = new Object();
? lock(thisLock) { ? }
?}
Isso resulta em todos os membros sendo bloqueados, incluindo os que não necessitam de sincronização. Caso precise de atualizações atômicas para uma variável de membro particular, use a classe System.Threading.Interlocked.
Coordenar vários leitores e escritores único utilizando ReaderWriterLock ao invés de bloqueio
Um monitor ou bloqueio que é levemente contestável é relativamente barato a partir de uma perspectiva de desempenho, mas torna-se mais dispendioso se for altamente contestável. O ReaderWriterLock fornece um mecanismo de bloqueio compartilhado e permite que múltiplos threads leiam um recurso simultaneamente, mas requer um thread para esperar um bloqueio exclusivo para escrever o recurso.
Você deve sempre tentar minimizar a duração de leituras e escritas. Escrita longa pode prejudicar o rendimento do aplicativo, porque o bloqueio de gravação é exclusivo. Leituras longas podem bloquear os outros threads de espera de leitura e escrita.
Não bloquear o tipo de objetos para fornecer acesso sincronizado
O mesmo exemplo de um tipo de objeto pode ser usado em vários domínios de aplicativo sem qualquer empacotamento ou clonagem. Se você implementar uma política de bloqueio do tipo de objeto usando lock(typeof (type)), você bloqueaia todas os objetos em domínios de aplicativo dentro do processo.
Um exemplo de bloqueio de todos os tipo se parece com isso:
lock(typeof(MyClass))
{
//custom code
}
Em vez disso, você pode fornecer um objeto estático em seu tipo, que pode ser bloqueado para fornecer acesso sincronizado.
class MyClass{
private static Object obj = new Object();
public void SomeFunc()
{
lock(obj)
{
//perform some operation
}
}
}
Evite também o bloqueio de outros aplicativos de domínio Agile, como strings, instâncias de montagem, ou um array de bytes, pela mesma razão.
***
Artigo original disponível em: http://blog.monitis.com/index.php/2012/04/23/improving-net-application-performance-part-8-locking-and-synchronization/
http://imasters.com.br/artigo/25031/dotnet/melhorando-desempenho-de-aplicativos-net-parte-08