El libro "Competencia en C #. Programación asincrónica, paralela y multiproceso. 2nd int. ed. "

imagenHola habrozhiteli! Si tiene miedo de la programación competitiva y multiproceso, este libro está escrito para usted. Stephen Cleary tiene 85 recetas para trabajar con .NET y C # 8.0 para procesamiento paralelo y programación asincrónica. La competencia ya se ha convertido en el método aceptado para desarrollar aplicaciones altamente escalables, pero la programación concurrente sigue siendo una tarea desalentadora. Los ejemplos detallados y los comentarios sobre el código ayudarán a comprender cómo las herramientas modernas aumentan el nivel de abstracción y simplifican la programación competitiva. Aprenderá a usar asíncrono y esperar operaciones asincrónicas, expandirá las capacidades del código mediante el uso de hilos asincrónicos, explorará el potencial de la programación paralela con la biblioteca TPL Dataflow,cree canales de flujo de datos con la biblioteca TPL Dataflow, use el sistema basado en LINQ. Funcionalidad reactiva, use colecciones seguras e inmutables, realice pruebas unitarias de código competitivo, tome el control del grupo de hilos, implemente la cancelación cooperativa correcta, analice scripts para combinar métodos competitivos , use todas las características de la programación orientada a objetos compatible asincrónicamente, reconozca y cree adaptadores para el código que usa estilos antiguos de programación asincrónica.analice los scripts para combinar métodos competitivos, use todas las características de la programación orientada a objetos compatible asincrónicamente, reconozca y cree adaptadores para el código que usa viejos estilos de programación asincrónica.analice los scripts para combinar métodos competitivos, use todas las características de la programación orientada a objetos compatible asincrónicamente, reconozca y cree adaptadores para el código que usa viejos estilos de programación asincrónica.

Conceptos básicos de la programación paralela


4.1. Procesamiento de datos en paralelo.


Tarea


Hay una recopilación de datos. Debe realizar la misma operación con cada elemento de datos. Esta operación es computacionalmente limitada y puede llevar algún tiempo.

Decisión


El tipo Paralelo contiene el método ForEach, diseñado específicamente para esta tarea. El siguiente ejemplo obtiene una colección de matrices y rota estas matrices:

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

Puede haber situaciones en las que sea necesario abortar el ciclo prematuramente (por ejemplo, si se detecta un valor no válido). El siguiente ejemplo invierte cada matriz, pero si se encuentra una matriz no válida, el ciclo se interrumpirá:

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

Este código usa ParallelLoopState.Stop para detener el bucle y evitar más llamadas al cuerpo del bucle. Tenga en cuenta que el ciclo es paralelo, por lo tanto, ya pueden realizarse otras llamadas al cuerpo del ciclo, incluidas las llamadas a elementos que siguen al actual. En el ejemplo de código anterior, si la tercera matriz no es reversible, entonces el ciclo se interrumpe y no se procesarán nuevas matrices, pero puede resultar que otras matrices ya se están procesando (por ejemplo, la cuarta y la quinta).

Una situación más común ocurre cuando necesita cancelar un bucle paralelo. Esto no es lo mismo que detener un ciclo; el ciclo se detiene desde adentro y se cancela afuera. Por ejemplo, el botón cancelar puede cancelar CancellationTokenSource, cancelando el bucle paralelo, como en el siguiente ejemplo:

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

Debe tenerse en cuenta que cada tarea paralela puede realizarse en un hilo diferente, por lo tanto, cualquier estado conjunto debe estar protegido. El siguiente ejemplo invierte cada matriz y cuenta el número de matrices que no se pudieron invertir:

// :     .
//      
//    .
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;
}

Explicación


El método Parallel.ForEach proporciona procesamiento paralelo para una secuencia de valores. Una solución similar de Parallel LINQ (PLINQ) proporciona casi las mismas capacidades en sintaxis tipo LINQ. Una de las diferencias entre Parallel y PLINQ es que PLINQ asume que puede usar todos los núcleos de la computadora, mientras que Parallel puede responder dinámicamente a las condiciones cambiantes del procesador.

Parallel.ForEach implementa un bucle foreach paralelo. Si necesita ejecutar un ciclo for paralelo, la clase Parallel también admite el método Parallel.For. El método Parallel.For es especialmente útil cuando se trabaja con múltiples matrices de datos que reciben un solo índice.

Información Adicional


La receta 4.2 analiza la agregación paralela de una serie de valores, incluida la suma y el cálculo de promedios.

La receta 4.5 cubre los conceptos básicos de PLINQ.

El capítulo 10 trata sobre la cancelación.

4.2. Agregación paralela


Tarea


Se requiere agregar los resultados al final de la operación paralela (ejemplos de agregación son la suma de valores o el cálculo del promedio).

Decisión


Para admitir la agregación, la clase Paralela utiliza el concepto de valores locales, variables que existen localmente dentro de un bucle paralelo. Esto significa que el cuerpo del bucle simplemente puede acceder al valor directamente, sin necesidad de sincronización. Cuando el ciclo está listo para agregar todos sus resultados locales, lo hace usando el delegado localFinally. Cabe señalar que el delegado localFinally no necesita sincronizar el acceso a la variable para almacenar el resultado. Ejemplo de suma paralela:

// :     .
//      
//    .
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;
}

Parallel LINQ proporciona un soporte de agregación más completo que la clase Parallel:

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

Bien, eso fue un truco barato porque PLINQ tiene soporte incorporado para muchos operadores comunes (como Sum). PLINQ también proporciona soporte de agregación genérico con el operador Agregado:

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

Explicación


Si ya está usando la clase Paralelo, debe usar su soporte de agregación. En otros casos, el soporte de PLINQ suele ser más expresivo y el código es más corto.

Información Adicional


La receta 4.5 describe los conceptos básicos de PLINQ.

4.3. Llamada paralela


Tarea


Hay un conjunto de métodos que deberían llamarse en paralelo. Estos métodos son (en su mayoría) independientes entre sí.

Decisión


La clase paralela contiene un método de invocación simple diseñado para tales escenarios. En el siguiente ejemplo, la matriz se divide en dos y las dos mitades se procesan de forma independiente:

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)
{
   // ,   ...
}

También puede pasar una serie de delegados al método Parallel.Invoke si se desconoce el número de llamadas antes de la ejecución:

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

Parallel.Invoke admite la cancelación, como otros métodos de la clase Parallel:

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

Explicación


El método Parallel.Invoke es una gran solución para una simple llamada paralela. Observo que no es tan adecuado para situaciones en las que es necesario activar una acción para cada elemento de datos de entrada (es mejor usar Parallel.ForEach para esto), o si cada acción produce algún resultado (en su lugar, se debe usar Parallel LINQ).

Información Adicional


La receta 4.1 analiza el método Parallel.ForEach, que realiza una acción para cada elemento de datos.

La receta 4.5 se ocupa de LINQ en paralelo.

Sobre el Autor


Stephen Cleary , un desarrollador experimentado, ha pasado de ARM a Azure. Contribuyó a la biblioteca de código abierto Boost C ++ y lanzó varias bibliotecas y utilidades propietarias.

»Se puede encontrar más información sobre el libro en el sitio web del editor
» Contenido
» Extracto de

Khabrozhiteley 25% de descuento en el cupón - Cleary

Al pagar la versión en papel del libro, se envía un libro electrónico por correo electrónico.

All Articles