O livro "Competição em c #. Programação assíncrona, paralela e multithread. 2nd int. ed. "

imagemOlá, habrozhiteli! Se você tem medo de programação competitiva e multithread, este livro foi escrito para você. Stephen Cleary tem 85 receitas para trabalhar com .NET e C # 8.0 para processamento paralelo e programação assíncrona. A concorrência já se tornou o método aceito para o desenvolvimento de aplicativos altamente escalonáveis, mas a programação simultânea continua sendo uma tarefa assustadora. Exemplos e comentários detalhados sobre o código ajudarão a entender como as ferramentas modernas aumentam o nível de abstração e simplificam a programação competitiva. Você aprenderá como usar async e aguardará operações assíncronas, expandirá os recursos do código através do uso de threads assíncronos, explorará o potencial da programação paralela com a biblioteca TPL Dataflow,Crie pipelines de fluxo de dados com a biblioteca TPL Dataflow, use a funcionalidade System.Reactive, use coleções imutáveis ​​e seguras para threads, realize testes de unidade de código competitivo, assuma o controle do pool de threads, controle o pool de threads, implemente o cancelamento cooperativo correto, analise scripts para combinar métodos competitivos , use todos os recursos da programação orientada a objeto compatível com assincronia, reconheça e crie adaptadores para código que usa estilos antigos de programação assíncrona.analise scripts para combinar métodos competitivos, use todos os recursos da programação orientada a objetos compatível com assincronia, reconheça e crie adaptadores para código que usa estilos antigos de programação assíncrona.analise scripts para combinar métodos competitivos, use todos os recursos da programação orientada a objetos compatível com assincronia, reconheça e crie adaptadores para código que usa estilos antigos de programação assíncrona.

Noções básicas de programação paralela


4.1 Processamento de dados paralelo


Tarefa


Há uma coleção de dados. Você deve executar a mesma operação com cada item de dados. Esta operação é computacionalmente limitada e pode levar algum tempo.

Decisão


O tipo Parallel contém o método ForEach, projetado especificamente para esta tarefa. O exemplo a seguir obtém uma coleção de matrizes e gira essas matrizes:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees)
{
   Parallel.ForEach(matrices, matrix => matrix.Rotate(degrees));
}

Pode haver situações em que seja necessário interromper o ciclo prematuramente (por exemplo, se um valor inválido for detectado). O exemplo a seguir inverte cada matriz, mas se uma matriz inválida for encontrada, o loop será interrompido:

void InvertMatrices(IEnumerable<Matrix> matrices)
{
   Parallel.ForEach(matrices, (matrix, state) =>
   {
      if (!matrix.IsInvertible)
        state.Stop();
      else
        matrix.Invert();
   });
}

Esse código usa ParallelLoopState.Stop para interromper o loop e impedir outras chamadas ao corpo do loop. Lembre-se de que o ciclo é paralelo; portanto, outras chamadas para o corpo do ciclo já podem ser feitas, incluindo chamadas para elementos após a atual. No exemplo de código fornecido, se a terceira matriz não for reversível, o ciclo será interrompido e novas matrizes não serão processadas, mas pode acontecer que outras matrizes já estejam sendo processadas (por exemplo, quarta e quinta).

Uma situação mais comum ocorre quando você precisa cancelar um loop paralelo. Isso não é o mesmo que parar um ciclo; o ciclo para de dentro e é cancelado do lado de fora. Por exemplo, o botão cancelar pode cancelar o CancellationTokenSource, cancelando o loop paralelo, como no exemplo a seguir:

void RotateMatrices(IEnumerable<Matrix> matrices, float degrees,
      CancellationToken token)
{
   Parallel.ForEach(matrices,
         new ParallelOptions { CancellationToken = token },
         matrix => matrix.Rotate(degrees));
}

Deve-se ter em mente que cada tarefa paralela pode ser executada em um encadeamento diferente; portanto, qualquer estado conjunto deve ser protegido. O exemplo a seguir inverte cada matriz e conta o número de matrizes que não puderam ser invertidas:

// :     .
//      
//    .
int InvertMatrices(IEnumerable<Matrix> matrices)
{
  object mutex = new object();
  int nonInvertibleCount = 0;
  Parallel.ForEach(matrices, matrix =>
  {
     if (matrix.IsInvertible)
    {
       matrix.Invert();
    }
    else
    {
       lock (mutex)
      {
         ++nonInvertibleCount;
      }
    }
  });
  return nonInvertibleCount;
}

Explicação


O método Parallel.ForEach fornece processamento paralelo para uma sequência de valores. Uma solução LINQ paralela (PLINQ) semelhante fornece quase os mesmos recursos na sintaxe semelhante ao LINQ. Uma das diferenças entre o Parallel e o PLINQ é que o PLINQ assume que ele pode usar todos os núcleos no computador, enquanto o Parallel pode responder dinamicamente às mudanças nas condições do processador.

Parallel.ForEach implementa um loop foreach paralelo. Se você precisar executar um loop for paralelo, a classe Parallel também suporta o método Parallel.For. O método Parallel.For é especialmente útil ao trabalhar com várias matrizes de dados que recebem um único índice.

informação adicional


A receita 4.2 discute a agregação paralela de uma série de valores, incluindo a soma e o cálculo de médias.

A receita 4.5 cobre os conceitos básicos do PLINQ.

O capítulo 10 trata do cancelamento.

4.2 Agregação paralela


Tarefa


É necessário agregar os resultados no final da operação paralela (exemplos de agregação são soma de valores ou cálculo da média).

Decisão


Para dar suporte à agregação, a classe Parallel usa o conceito de valores locais - variáveis ​​que existem localmente em um loop paralelo. Isso significa que o corpo do loop pode simplesmente acessar o valor diretamente, sem a necessidade de sincronização. Quando o loop está pronto para agregar todos os resultados locais, ele faz isso usando o delegado localFinally. Observe que o delegado localFinally não precisa sincronizar o acesso à variável para armazenar o resultado. Exemplo de somatório paralelo:

// :     .
//      
//    .
int ParallelSum(IEnumerable<int> values)
{
  object mutex = new object();
  int result = 0;
  Parallel.ForEach(source: values,
        localInit: () => 0,
        body: (item, state, localValue) => localValue + item,
        localFinally: localValue =>
       {
          lock (mutex)
             result += localValue;
       });
  return result;
}

O LINQ paralelo fornece suporte de agregação mais abrangente do que a classe Parallel:

int ParallelSum(IEnumerable<int> values)
{
   return values.AsParallel().Sum();
}

Ok, isso foi um truque barato, porque o PLINQ possui suporte interno para muitos operadores comuns (como Sum). O PLINQ também fornece suporte à agregação genérica com o operador Agregado:

int ParallelSum(IEnumerable<int> values)
{
  return values.AsParallel().Aggregate(
        seed: 0,
        func: (sum, item) => sum + item
  );
}

Explicação


Se você já estiver usando a classe Parallel, deverá usar seu suporte de agregação. Em outros casos, o suporte ao PLINQ é geralmente mais expressivo e o código é mais curto.

informação adicional


A receita 4.5 descreve os princípios básicos do PLINQ.

4.3 Chamada paralela


Tarefa


Há um conjunto de métodos que devem ser chamados em paralelo. Esses métodos são (principalmente) independentes um do outro.

Decisão


A classe Parallel contém um método Invoke simples projetado para esses cenários. No exemplo a seguir, a matriz é dividida em duas e duas metades são processadas independentemente:

void ProcessArray(double[] array)
{
   Parallel.Invoke(
         () => ProcessPartialArray(array, 0, array.Length / 2),
         () => ProcessPartialArray(array, array.Length / 2, array.Length)
   );
}

void ProcessPartialArray(double[] array, int begin, int end)
{
   // ,   ...
}

Você também pode passar uma matriz de delegados para o método Parallel.Invoke se o número de chamadas for desconhecido antes da execução:

void DoAction20Times(Action action)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(actions);
}

Parallel.Invoke suporta cancelamento, como outros métodos da classe Parallel:

void DoAction20Times(Action action, CancellationToken token)
{
   Action[] actions = Enumerable.Repeat(action, 20).ToArray();
   Parallel.Invoke(new ParallelOptions { CancellationToken = token },
        actions);
}

Explicação


O método Parallel.Invoke é uma ótima solução para uma chamada paralela simples. Observo que não é tão adequado para situações em que é necessário ativar uma ação para cada elemento de dados de entrada (é melhor usar Parallel.ForEach para isso) ou se cada ação produz alguma saída (o LINQ paralelo deve ser usado).

informação adicional


A receita 4.1 discute o método Parallel.ForEach, que executa uma ação para cada item de dados.

A receita 4.5 lida com o LINQ paralelo.

Sobre o autor


Stephen Cleary , um desenvolvedor experiente, passou do ARM para o Azure. Ele contribuiu para a biblioteca Boost C ++ de código aberto e lançou várias bibliotecas e utilitários proprietários.

»Mais informações sobre o livro podem ser encontradas no site da editora
» Conteúdo
» Trecho

Para Khabrozhiteley desconto de 25% no cupom - Cleary

Após o pagamento da versão impressa do livro, um livro eletrônico é enviado por e-mail.

All Articles