Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

RSS iMasters

[Resolvido] Melhorando desempenho de aplicativos .NET - Parte 08

Recommended Posts

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:

  • Lock (C #) - O compilador C# converte a declaração de bloqueio Monitor.Enter e Monitor.Exit em chamadas em torno de uma tentativa, ou concretização do bloqueio (utilize o SyncLock em Visual Basic. NET);
  • Classe WaitHandle - Esta classe tem a funcionalidade de esperar pelo acesso exclusivo para múltiplos objetos, ao mesmo tempo. Existem três derivados de WaitHandle:
  1. ManualResetEvent - Permite que o código espere um sinal que é resetado manualmente.
  2. AutoResetEvent - Isso permite que o código espere um sinal que é resetado automaticamente.
  3. Mutex - Esta é uma versão especializada do WaitHandle que suporta processo cruzado de uso. O objeto Mutex só pode ser fornecido com um único nome, de modo que uma referência para o objeto Mutex não se faz necessária. Código em diferentes processos podem acessar o mesmo Mutex pelo nome.
  • 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;
  • Objeto Monitor - Este fornece métodos estáticos para sincronizar o acesso a tipos de referência. Ele também fornece métodos substitutos para permitir que o código tente bloquear por um período específico. A classe Monitor não pode ser usada com os tipos de valor, que são empacotados quando usados com o Monitor e cada tentativa para bloquear gera um novo objeto empacotado, que é diferente do resto - o que anula qualquer acesso exclusivo. O C# fornece uma mensagem de erro se você usar um Monitor em um tipo de valor.

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;
  • Objeto específico que é um membro de uma classe - Isso dá opções às outras duas opções. Bloqueie em um objeto private static se você precisar de sincronização no nível de classe. Bloqueie em um objeto private (que não é static), se você precisar sincronizar apenas no nível de um tipo. Implemente sua política de bloqueio de forma coerente e claramente, em cada método em questão.

Enquanto faz o bloqueio, você também deve considerar a granularidade de seus bloqueios. As opções são como as seguintes:

  • Método - Você deve considerar o bloqueio em nível de método apenas quando todas as linhas de código no método precisarem ter acesso sincronizado, caso contrário, isso pode resultar em aumento de contenção;
  • Bloqueio de código em um método - A maioria de suas necessidades pode ser satisfeita escolhendo um objeto escopado como o bloqueio e por ter uma política onde você adquire esse bloqueio pouco antes de colocar o código que altera o estado compartilhado que o bloqueio protege. Travando objetos, você garante que apenas um dos pedaços de código que bloqueia o objeto será executado de cada vez.

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;
  • Considere um vínculo fraco para reduzir problemas de concorrência. Por exemplo, considere o uso do modelo de evento de delegação para minimizar a contenção de bloqueio.

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:

  1. O estado pode ou não precisar de um thread safe. Por padrão, as classes não devem ser thread-safe, pois se são usadas em um ambiente de um único thread ou sincronizadas, tornandá-las thread safe adiciona uma sobrecarga.

    Adicionar bloqueios para criar código thread-safe diminui o desempenho e aumenta a contenção de bloqueio. Em modelos de aplicativos comuns, apenas um thread por vez executa o código do usuário, o que minimiza a necessidade de thread safety.

  2. Considere thread safety para dados estáticos. Se você vai usar estados estáticos, considere a forma de protegê-lo de acesso simultâneo por vários threads ou pedidos múltiplos. Em cenários comuns de servidores, dados estáticos são compartilhados entre solicitações, o que significa que vários segmentos podem executar esse código ao mesmo tempo.

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

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.