Partager via


Comment : utiliser la gestion des exceptions pour rompre une boucle parallèle

Cette rubrique indique comment écrire un algorithme de recherche pour une arborescence de base.

La rubrique Annulation dans la bibliothèque de modèles parallèles explique le rôle de l'annulation dans la Bibliothèque de modèles parallèles. Pour annuler un travail parallèle, utiliser la gestion des exceptions est moins efficace que d'utiliser les méthodes concurrency::task_group::cancel et concurrency::structured_task_group::cancel. Toutefois, l'utilisation de la gestion des exceptions pour annuler un travail est adaptée dans le scénario suivant : lorsque vous appelez une bibliothèque tierce qui utilise des tâches ou des algorithmes parallèles, mais qui ne fournit pas d'objet task_group ou structured_task_group à annuler.

Exemple

L'exemple suivant montre un type tree de base qui contient un élément de données et une liste de nœuds enfants. La section suivante montre le corps de la méthode for_all, qui exécute une fonction de travail de manière récursive sur chaque nœud enfant.

// A simple tree structure that has multiple child nodes. 
template <typename T>
class tree
{
public:
   explicit tree(T data)
      : _data(data)
   {
   }

   // Retrieves the data element for the node. 
   T get_data() const
   {
      return _data;
   }

   // Adds a child node to the tree. 
   void add_child(tree& child)
   {
      _children.push_back(child);
   }

   // Performs the given work function on the data element of the tree and 
   // on each child. 
   template<class Function>
   void for_all(Function& action);

private:
   // The data for this node.
   T _data;
   // The child nodes.
   list<tree> _children;
};

L'exemple suivant illustre la méthode for_all. Il utilise l'algorithme concurrency::parallel_for_each pour exécuter une fonction de travail sur chaque nœud de l'arborescence en parallèle.

// Performs the given work function on the data element of the tree and 
// on each child. 
template<class Function>
void for_all(Function& action)
{
   // Perform the action on each child.
   parallel_for_each(begin(_children), end(_children), [&](tree& child) {
      child.for_all(action);
   });

   // Perform the action on this node.
   action(*this);
}

L'exemple suivant illustre la fonction search_for_value, qui recherche une valeur dans l'objet tree fourni. Cette fonction passe à la méthode for_all une fonction de travail qui lève une exception lorsqu'elle détecte un nœud d'arborescence qui contient la valeur fournie.

Supposez que la classe tree est fournie par une bibliothèque tierce et que vous ne pouvez pas la modifier. Dans ce cas, l'utilisation de la gestion des exceptions est appropriée, car la méthode for_all ne fournit pas d'objet task_group ou structured_task_group à l'appelant. Par conséquent, la fonction de travail est incapable d'annuler directement son groupe de tâches parent.

Lorsque la fonction de travail que vous fournissez à un groupe de tâches lève une exception, le runtime arrête toutes les tâches qui sont dans le groupe de tâches (y compris les groupes de tâches enfants) et ignore les tâches qui n'ont pas encore démarré. La fonction search_for_value utilise un bloc try-catch pour capturer l'exception et imprimer le résultat sur la console.

// Searches for a value in the provided tree object. 
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function 
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

L'exemple suivant crée un objet tree et recherche plusieurs valeurs en parallèle dans cet objet. La fonction build_tree est illustrée plus loin dans cette rubrique.

int wmain()
{  
   // Build a tree that is four levels deep with the initial level  
   // having three children. The value of each node is a random number.
   mt19937 gen(38);
   tree<int> t = build_tree<int>(4, 3, [&gen]{ return gen()%100000; });

   // Search for a few values in the tree in parallel.
   parallel_invoke(
      [&t] { search_for_value(t, 86131); },
      [&t] { search_for_value(t, 17522); },
      [&t] { search_for_value(t, 32614); }
   );
}

This example uses the concurrency::parallel_invoke algorithm to search for values in parallel. Pour plus d'informations sur cet algorithme, consultez Algorithmes parallèles.

L'exemple complet suivant utilise la gestion des exceptions pour rechercher des valeurs dans une arborescence de base.

// task-tree-search.cpp 
// compile with: /EHsc
#include <ppl.h>
#include <list>
#include <iostream>
#include <algorithm>
#include <sstream>
#include <random>

using namespace concurrency;
using namespace std;

// A simple tree structure that has multiple child nodes. 
template <typename T>
class tree
{
public:
   explicit tree(T data)
      : _data(data)
   {
   }

   // Retrieves the data element for the node. 
   T get_data() const
   {
      return _data;
   }

   // Adds a child node to the tree. 
   void add_child(tree& child)
   {
      _children.push_back(child);
   }

   // Performs the given work function on the data element of the tree and 
   // on each child. 
   template<class Function>
   void for_all(Function& action)
   {
      // Perform the action on each child.
      parallel_for_each(begin(_children), end(_children), [&](tree& child) {
         child.for_all(action);
      });

      // Perform the action on this node.
      action(*this);
   }

private:
   // The data for this node.
   T _data;
   // The child nodes.
   list<tree> _children;
};

// Builds a tree with the given depth.  
// Each node of the tree is initialized with the provided generator function. 
// Each level of the tree has one more child than the previous level. 
template <typename T, class Generator>
tree<T> build_tree(int depth, int child_count, Generator& g)
{
   // Create the tree node.
   tree<T> t(g());

   // Add children. 
   if (depth > 0)
   {
      for(int i = 0; i < child_count; ++i)
      {
         t.add_child(build_tree<T>(depth - 1, child_count + 1, g));
      }
   }

   return t;
}

// Searches for a value in the provided tree object. 
template <typename T>
void search_for_value(tree<T>& t, int value)
{
   try
   {
      // Call the for_all method to search for a value. The work function 
      // throws an exception when it finds the value.
      t.for_all([value](const tree<T>& node) {
         if (node.get_data() == value)
         {
            throw &node;
         }
      });
   }
   catch (const tree<T>* node)
   {
      // A matching node was found. Print a message to the console.
      wstringstream ss;
      ss << L"Found a node with value " << value << L'.' << endl;
      wcout << ss.str();
      return;
   }

   // A matching node was not found. Print a message to the console.
   wstringstream ss;
   ss << L"Did not find node with value " << value << L'.' << endl;
   wcout << ss.str();   
}

int wmain()
{  
   // Build a tree that is four levels deep with the initial level  
   // having three children. The value of each node is a random number.
   mt19937 gen(38);
   tree<int> t = build_tree<int>(4, 3, [&gen]{ return gen()%100000; });

   // Search for a few values in the tree in parallel.
   parallel_invoke(
      [&t] { search_for_value(t, 86131); },
      [&t] { search_for_value(t, 17522); },
      [&t] { search_for_value(t, 32614); }
   );
}

Cet exemple génère l'exemple de sortie suivant.

  

Compilation du code

Copiez l'exemple de code et collez-le dans un projet Visual Studio, ou collez-le dans un fichier nommé task-tree-search.cpp puis exécutez la commande suivante dans une fenêtre d'invite de commandes Visual Studio .

cl.exe /EHsc task-tree-search.cpp

Voir aussi

Référence

task_group, classe

structured_task_group, classe

parallel_for_each, fonction

Concepts

Annulation dans la bibliothèque de modèles parallèles

Gestion des exceptions dans le runtime d'accès concurrentiel

Parallélisme des tâches (runtime d'accès concurrentiel)

Algorithmes parallèles