Compartilhar via


Práticas recomendadas de threads gerenciadas

Multithreading requer programação cuidadosa. Para a maioria das tarefas, você pode reduzir a complexidade enfileirando solicitações para execução por threads de um pool. Este tópico aborda situações mais difíceis, como coordenar o trabalho de várias threads, ou manipular threads que gerem bloqueios.

Observação

No .NET Framework versão 4, a biblioteca paralela de tarefas e PLINQ fornecem APIs que reduzir alguns complexidade e riscos da programação multithread.Para obter mais informações, consulte Programação em paralela a.NET Framework.

Bloqueios e condições Race

Multithreading resolve problemas com taxa de transferência e responsividade, mas ela introduz novos problemas ao fazer isso: deadlocks e condições de corrida.

Deadlocks

Um deadlock ocorre quando cada uma das dois threads tenta bloquear um recurso já bloquada pela outra. Nenhuma das duas threads consegue seguir adiante.

Muitos métodos das classes de segmentação gerenciados fornecem tempos limite para ajudá-lo detectar bloqueios. Por exemplo, o código a seguir tenta se conseguir um bloqueio na instância atual. Se o bloqueio não é obtido em 300 milissegundos, Monitor.TryEnter retorna false.

If Monitor.TryEnter(Me, 300) Then
    Try
        ' Place code protected by the Monitor here.
    Finally
        Monitor.Exit(Me)
    End Try
Else
    ' Code to execute if the attempt times out.
End If
if (Monitor.TryEnter(this, 300)) {
    try {
        // Place code protected by the Monitor here.
    }
    finally {
        Monitor.Exit(this);
    }
}
else {
    // Code to execute if the attempt times out.
}

Condições de corrida

Uma condição de corrida é um erro que ocorre quando o resultado de um programa depende do qual dos dois ou mais segmentos atinge um bloco de código específico primeiro. Executar o programa muitas vezes produz resultados diferentes, e o resultado de qualquer executar determinada não pode ser previsto.

Um exemplo simples de uma condição de corrida é incrementando um campo. Suponha que uma classe tem um private estático campo (compartilhado em Visual Basic) que é incrementado toda vez que uma instância da classe é criada, usando o código, como objCt++; (C#) ou objCt += 1 (Visual Basic). Esta operação requer carregar o valor de objCt em um registro, incrementar o valor e armazená-la em objCt.

Em um aplicativo com vários segmentos, um segmento que tenha carregado e incrementado o valor pode ser apropriado por outro segmento que executa todas as etapas três; quando o primeiro segmento reinicia a execução e armazena o valor, ele substitui objCt Sem levando em conta o fato de que o valor foi alterado nesse ínterim.

Essa condição de corrida específica é facilmente evitada usando métodos para o Interlocked de classe, como Interlocked.Increment. Para ler sobre outras técnicas para sincronizar dados entre vários threads, consulte Sincronizando dados para o Multithreading.

Condições de corrida também podem ocorrer quando você sincronizar as atividades de vários segmentos. Sempre que você escrever uma linha de código, você deve considerar o que poderia acontecer se um segmento foram apropriado antes de executar a linha (ou antes de qualquer das instruções da máquina individuais que compõem a linha), e outro segmento overtook-lo.

Número de processadores

Multithreading resolve problemas diferentes para os computadores processador único-que executar a maioria dos software usuário final, e os computadores multiprocessador normalmente usados como servidores.

Computadores Processor único

Multithreading fornece receptividade maior para o usuário de computador, e usa tempo ocioso para tarefas em segundo plano. Se você usar multithreading em um computador com um único processador:

  • Apenas um segmento é executada em qualquer instante.

  • Um segmento em segundo plano executa somente quando o segmento do usuário principal estiver ocioso. Um segmento de primeiro plano que executa constantemente starves segmentos Plano de de fundo de tempo do processador.

  • Quando você chama o Thread.Start método em um segmento, segmento não inicia a execução até que o segmento atual produz ou é executada pelo sistema operacional.

  • Condições de corrida geralmente ocorrem porque o programador não antecipou o fato que um segmento pode ser apropriado em um momento estranho, às vezes permitindo outro segmento para alcançar um bloco de códigos primeiro.

Computadores multiprocessadores

Multithreading fornece maior taxa de transferência. Processadores dez podem fazer o trabalho de um, dez vezes mas somente se o trabalho é dividido para que todos os dez pode estar trabalhando uma vez; segmentos fornecer uma maneira fácil de divida o trabalho e explorar o poder de processamento extra. Se você usar multithreading em um computador multiprocessador:

  • O número de segmentos que podem executar simultaneamente é limitado pelo número de processadores.

  • Um segmento em segundo plano executa somente quando o número de segmentos de primeiro plano executar é menor que o número de processadores.

  • Quando você chama o Thread.Start o método em um segmento, esse segmento pode ou não pode iniciar a execução imediatamente, dependendo do número de processadores e o número de segmentos que estão aguardando para executar.

  • Condições de corrida podem ocorrer porque não apenas segmentos são apropriados inesperadamente, mas porque dois segmentos executando em diferentes processadores podem ser racing para alcançar o mesmo bloco de código.

Membros estáticos e construtores estáticos

Uma classe não é inicializada até seu construtor de classe (static construtor no C#, Shared Sub New em Visual Basic) concluiu a execução. Para impedir a execução de código em um tipo que não foi inicializado, o common language runtime bloqueia todas as chamadas de outros threads para static membros da classe (Shared membros em Visual Basic) até que o construtor da classe tenha terminado em execução.

Por exemplo, se um construtor de classe inicia um novo thread e o procedimento de segmento chama um static o membro da classe, novos blocos de thread até que o construtor da classe seja concluída.

Isso se aplica a qualquer tipo que pode ter um static construtor.

Recomendações gerais

Considere as seguintes diretrizes ao usando vários encadeamentos:

  • Não use Thread.Abort para finalizar outros segmentos. Chamada Abort em outro segmento é akin para lançar uma exceção em que segmento, sem saber que aponte esse segmento tiver alcançado no seu processamento.

  • Não use Thread.Suspend e Thread.Resume para sincronizar as atividades de vários segmentos. Do use Mutex, ManualResetEvent, AutoResetEvent, and Monitor.

  • Não controlar a execução de segmentos de trabalho do seu programa principal (usando eventos, por exemplo). Em vez disso, criar seu programa de modo que segmentos de trabalho são responsáveis por aguardando até que trabalho esteja disponível, executando-la, e notificar outras partes do seu programa quando terminar. Não use Thread.Suspend e Thread.Resume Para sincronizar as atividades de vários segmentos. Monitor.PulseAllé útil em situações onde o operador threads bloco.

  • Não use tipos como objetos de bloqueio. Isto é, evite código como lock(typeof(X)) em C# ou SyncLock(GetType(X)) em Visual Basic, ou o uso de Monitor.Enter com Type objetos. Para um determinado tipo, há apenas uma instância de System.Type por domínio de aplicativo. Se o tipo que você assumir um bloqueio for público, o código diferente do seu próprio pode levar bloqueios, levando a deadlocks. Para problemas adicionais, consulte As práticas recomendadas de confiabilidade.

  • Tenha cuidado ao bloqueio em instâncias, por exemplo lock(this) em C# ou SyncLock(Me) Visual Basic. Se outro código em seu aplicativo externo para o tipo, leva a um bloqueio no objeto, os deadlocks podem ocorrer.

  • Garanta que um thread que entrou em um monitor sempre deixa o monitor, mesmo se ocorrer uma exceção, enquanto o thread está no monitor. C# lock declaração e a Visual Basic SyncLock instrução fornecem esse comportamento automaticamente, empregando uma Finalmente bloco para garantir que Monitor.Exit é chamado. Se você não pode garantir que Exit será chamado, considere alterar o design para usar Mutex. Um mutex automaticamente é liberado quando termina o segmento que atualmente possui ele.

  • Usar vários segmentos para tarefas que requerem recursos diferentes, e evite atribuir vários segmentos a um único recurso. Por exemplo, qualquer tarefa que envolva E/s beneficia de ter seu próprio segmento, porque esse segmento será bloquear durante operações de E/s e assim permitir que outros segmentos para executar. Entrada do usuário é outro recurso que beneficia de um segmento dedicado. Em um computador com um único processador, uma tarefa que envolva cálculo intenso coexists com entrada do usuário e com tarefas que envolvam E/s, mas várias tarefas muito computation-contend com os outros.

  • Considere o uso de métodos para a Interlocked classe para alterações de estado simples, em vez de usar o lock instrução (SyncLock em Visual Basic). O lock a declaração é uma boa ferramenta de uso geral, mas o Interlocked classe fornece melhor desempenho para as atualizações que devem ser atômica. Internamente, ele executa um prefixo de bloqueio único se não houver nenhum conflito. Em análises de código assista para código como essa mostrado nos exemplos a seguir. No primeiro exemplo, uma variável de estado é incrementado:

    SyncLock lockObject
        myField += 1
    End SyncLock
    
    lock(lockObject) 
    {
        myField++;
    }
    

    Você pode melhorar o desempenho usando o Increment método em vez da lock instrução, da seguinte maneira:

    System.Threading.Interlocked.Increment(myField)
    
    System.Threading.Interlocked.Increment(myField);
    

    Observação

    No.NET Framework versão 2.0, o Add método fornece atômicas atualizações em incrementos maiores que 1.

    No segundo exemplo, uma variável do tipo de referência é atualizada somente se ela for uma referência nula (Nothing em Visual Basic).

    If x Is Nothing Then
        SyncLock lockObject
            If x Is Nothing Then
                x = y
            End If
        End SyncLock
    End If
    
    if (x == null)
    {
        lock (lockObject)
        {
            if (x == null)
            {
                x = y;
            }
        }
    }
    

    Pode ser melhorado o desempenho usando o CompareExchange método em vez disso, da seguinte maneira:

    System.Threading.Interlocked.CompareExchange(x, y, Nothing)
    
    System.Threading.Interlocked.CompareExchange(ref x, y, null);
    

    Observação

    No.NET Framework versão 2.0, o CompareExchange método tem uma sobrecarga genérica que pode ser usada para substituição de tipo seguro de qualquer tipo de referência.

Recomendações para bibliotecas de classe

Considerar as seguintes diretrizes ao criar bibliotecas de classes para multithreading:

  • Evitar a necessidade de sincronização, se possível. Isso é especialmente verdadeiro para código muito usado. Por exemplo, um algoritmo pode ser ajustado para tolerate uma condição de corrida em vez de eliminá-lo. Sincronização desnecessária diminui o desempenho e cria a possibilidade de bloqueios e condições de corrida.

  • Verifique os dados estáticos (Shared em Visual Basic) thread-safe por padrão.

  • Não faça segmento de dados da instância seguro por padrão. Adicionar bloqueios para criar código isenta de segmentos diminui o desempenho, aumenta contenção de bloqueio, e cria a possibilidade de bloqueios para ocorrer. Em comum modelos do aplicativo, apenas um segmento por vez executa código do usuário, que reduz a necessidade de segurança do segmento. Por esse motivo, as bibliotecas de classes .NET Framework são não segmento seguro por padrão.

  • Evite fornecer métodos estáticos que alterar estado estático. Nos cenários comuns, do servidor estado estático é compartilhado entre solicitações, que significa vários segmentos podem executar esse código ao mesmo tempo. Isso abrirá a possibilidade de segmentação bugs Backup. Considere usar um padrão de design que encapsula dados em instâncias que não são compartilhadas nas solicitações. Além disso, se dados estáticos estiverem sincronizados, chamadas entre métodos estáticos que alterar estado podem resultar em bloqueios ou redundante sincronização, afetar negativamente o desempenho.

Consulte também

Conceitos

Segmentos e Threading

Outros recursos

Threads gerenciadas