Le livre "Competition in C #. Programmation asynchrone, parallèle et multithread. 2e int. éd. "

imageBonjour, habrozhiteli! Si vous avez peur d'une programmation compétitive et multithread, ce livre est écrit pour vous. Stephen Cleary a 85 recettes pour travailler avec .NET et C # 8.0 pour le traitement parallèle et la programmation asynchrone. La concurrence est déjà devenue la méthode acceptée pour développer des applications hautement évolutives, mais la programmation simultanée reste une tâche ardue. Des exemples détaillés et des commentaires sur le code aideront à comprendre comment les outils modernes augmentent le niveau d'abstraction et simplifient la programmation compétitive. Vous apprendrez à utiliser async et attendez les opérations asynchrones, étendez les capacités du code grâce à l'utilisation de threads asynchrones, explorez le potentiel de la programmation parallèle avec la bibliothèque de flux de données TPL,créer des pipelines de flux de données avec la bibliothèque de flux de données TPL, utiliser le système LINQ.Fonctionnalité réactive, utiliser des collections thread-safe et immuables, effectuer des tests unitaires de code concurrentiel, prendre le contrôle du pool de threads, implémenter l'annulation coopérative correcte, analyser les scripts pour combiner des méthodes compétitives , utilisez toutes les fonctionnalités de la programmation orientée objet compatible asynchrone, reconnaissez et créez des adaptateurs pour le code qui utilise les anciens styles de programmation asynchrone.analyser des scripts pour combiner des méthodes compétitives, utiliser toutes les fonctionnalités d'une programmation orientée objet compatible asynchrone, reconnaître et créer des adaptateurs pour du code qui utilise d'anciens styles de programmation asynchrone.analyser des scripts pour combiner des méthodes compétitives, utiliser toutes les fonctionnalités d'une programmation orientée objet compatible asynchrone, reconnaître et créer des adaptateurs pour du code qui utilise d'anciens styles de programmation asynchrone.

Bases de la programmation parallèle


4.1. Traitement parallèle des données


Tâche


Il y a une collection de données. Vous devez effectuer la même opération avec chaque élément de données. Cette opération est limitée en termes de calcul et peut prendre un certain temps.

Décision


Le type Parallèle contient la méthode ForEach, conçue spécifiquement pour cette tâche. L'exemple suivant obtient une collection de matrices et fait pivoter ces matrices:

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

Il peut y avoir des situations dans lesquelles il est nécessaire d'interrompre le cycle prématurément (par exemple, si une valeur non valide est détectée). L'exemple suivant inverse chaque matrice, mais si une matrice non valide est trouvée, la boucle sera interrompue:

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

Ce code utilise ParallelLoopState.Stop pour arrêter la boucle et empêcher tout autre appel au corps de la boucle. Gardez à l'esprit que le cycle est parallèle, par conséquent, d'autres appels au corps du cycle peuvent déjà être effectués, y compris des appels d'éléments suivant le cycle actuel. Dans l'exemple de code donné, si la troisième matrice n'est pas réversible, le cycle est interrompu et de nouvelles matrices ne seront pas traitées, mais il peut s'avérer que d'autres matrices sont déjà en cours de traitement (par exemple, les quatrième et cinquième).

Une situation plus courante se produit lorsque vous devez annuler une boucle parallèle. Ce n'est pas la même chose que d'arrêter un cycle; le cycle s'arrête de l'intérieur et s'annule à l'extérieur. Par exemple, le bouton Annuler peut annuler le CancellationTokenSource, annulant la boucle parallèle, comme dans l'exemple suivant:

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

Il convient de garder à l'esprit que chaque tâche parallèle peut être effectuée dans un thread différent, par conséquent, tout état conjoint doit être protégé. L'exemple suivant inverse chaque matrice et compte le nombre de matrices qui n'ont pas pu être inversées:

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

Explication


La méthode Parallel.ForEach fournit un traitement parallèle pour une séquence de valeurs. Une solution Parallel LINQ (PLINQ) similaire offre presque les mêmes capacités dans une syntaxe de type LINQ. L'une des différences entre Parallel et PLINQ est que PLINQ suppose qu'il peut utiliser tous les cœurs de l'ordinateur, tandis que Parallel peut répondre dynamiquement aux conditions changeantes du processeur.

Parallel.ForEach implémente une boucle foreach parallèle. Si vous devez exécuter une boucle parallèle pour, la classe Parallel prend également en charge la méthode Parallel.For. La méthode Parallel.For est particulièrement utile lorsque vous travaillez avec plusieurs tableaux de données qui reçoivent un seul index.

Information additionnelle


La recette 4.2 traite de l'agrégation parallèle d'une série de valeurs, y compris la somme et le calcul des moyennes.

La recette 4.5 couvre les bases de PLINQ.

Le chapitre 10 traite de l'annulation.

4.2. Agrégation parallèle


Tâche


Il est nécessaire d'agréger les résultats à la fin de l'opération parallèle (des exemples d'agrégation sont la somme des valeurs ou le calcul de la moyenne).

Décision


Pour prendre en charge l'agrégation, la classe Parallel utilise le concept de valeurs locales - des variables qui existent localement dans une boucle parallèle. Cela signifie que le corps de la boucle peut simplement accéder directement à la valeur, sans avoir besoin de synchronisation. Lorsque la boucle est prête à regrouper tous ses résultats locaux, elle le fait à l'aide du délégué localFinally. Il convient de noter que le délégué localFinally n'a pas besoin de synchroniser l'accès à la variable pour stocker le résultat. Exemple de sommation parallèle:

// :     .
//      
//    .
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 offre un support d'agrégation plus complet que la classe Parallel:

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

D'accord, c'était une astuce bon marché car PLINQ a un support intégré pour de nombreux opérateurs communs (comme Sum). PLINQ fournit également un support d'agrégation générique avec l'opérateur Aggregate:

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

Explication


Si vous utilisez déjà la classe Parallel, vous devez utiliser son support d'agrégation. Dans d'autres cas, la prise en charge de PLINQ est généralement plus expressive et le code est plus court.

Information additionnelle


La recette 4.5 décrit les bases de PLINQ.

4.3. Appel parallèle


Tâche


Il existe un ensemble de méthodes qui doivent être appelées en parallèle. Ces méthodes sont (pour la plupart) indépendantes les unes des autres.

Décision


La classe Parallel contient une méthode Invoke simple conçue pour de tels scénarios. Dans l'exemple suivant, le tableau est divisé en deux et les deux moitiés sont traitées indépendamment:

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

Vous pouvez également transmettre un tableau de délégués à la méthode Parallel.Invoke si le nombre d'appels est inconnu avant l'exécution:

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

Parallel.Invoke prend en charge l'annulation, comme les autres méthodes de la classe Parallel:

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

Explication


La méthode Parallel.Invoke est une excellente solution pour un simple appel parallèle. Je note qu'il n'est pas si bien adapté aux situations dans lesquelles il est nécessaire d'activer une action pour chaque élément de données d'entrée (il est préférable d'utiliser Parallel.ForEach pour cela), ou si chaque action produit une sortie (Parallel LINQ doit être utilisé à la place).

Information additionnelle


La recette 4.1 décrit la méthode Parallel.ForEach, qui exécute une action pour chaque élément de données.

La recette 4.5 traite de Parallel LINQ.

A propos de l'auteur


Stephen Cleary , un développeur expérimenté, est passé d'ARM à Azure. Il a contribué à la bibliothèque open source Boost C ++ et a publié plusieurs bibliothèques et utilitaires propriétaires.

»Plus d'informations sur le livre peuvent être trouvées sur le site Web de l'éditeur
» Contenu
» Extrait

pour Khabrozhiteley 25% de réduction sur le coupon - Cleary

Lors du paiement de la version papier du livre, un livre électronique est envoyé par e-mail.

All Articles