Compartir a través de


Paralelismo de tareas (Runtime de simultaneidad)

En este documento se describe el rol de las tareas y los grupos de tareas en el runtime de simultaneidad. Use grupos de tareas cuando tenga dos o más elementos de trabajo independientes que desee ejecutar simultáneamente. Supongamos, por ejemplo, que tiene un algoritmo recursivo que divide el trabajo restante en dos particiones. Puede usar grupos de tareas para ejecutar estas particiones simultáneamente. Por el contrario, use algoritmos paralelos, como Concurrency::parallel_for, cuando desee aplicar la misma rutina a cada elemento de una colección en paralelo. Para obtener más información acerca de los algoritmos paralelos, vea Algoritmos paralelos.

Tareas y grupos de tareas

Una tarea es una unidad de trabajo que realiza un trabajo concreto. Una tarea puede ejecutarse normalmente en paralelo con otras tareas y se puede descomponer en tareas adicionales más específicas. Un grupo de tareas organiza una colección de tareas. Los grupos de tareas insertan las tareas en una cola de robo de trabajo. El programador quita las tareas de esta cola y las ejecuta con los recursos informáticos disponibles. Después de agregar tareas a un grupo de tareas, puede esperar a que finalicen todas las tareas o cancelar tareas que aún no se han iniciado.

PPL usa las clases Concurrency::task_group y Concurrency::structured_task_group para representar grupos de tareas, y la clase Concurrency::task_handle para representar tareas. La clase task_handle encapsula el código que realiza el trabajo. Este código tiene forma de una función lambda, un puntero a función o un objeto de función y se suele conocer como función de trabajo. Normalmente no es necesario trabajar con objetos task_handle directamente. En su lugar, se pasan las funciones de trabajo a un grupo de tareas, y el grupo de tareas crea y administra los objetos task_handle.

PPL divide los grupos de tareas en estas dos categorías: grupos de tareas no estructurados y grupos de tareas estructurados. La biblioteca PPL usa la clase task_group para representar grupos de tareas no estructurados y la clase structured_task_group para representar grupos de tareas estructurados.

Nota importanteImportante

La PPL también define el algoritmo Concurrency::parallel_invoke, que usa la clase structured_task_group para ejecutar un conjunto de tareas en paralelo. Como el algoritmo parallel_invoke tiene una sintaxis más concisa, se recomienda usarla en lugar de la clase structured_task_group si es posible. En el tema Algoritmos paralelos se describe con mayor detalle parallel_invoke.

Use parallel_invoke cuando tenga varias tareas independientes que desee ejecutar al mismo tiempo y deba esperar a que todas las tareas finalicen antes de continuar. Use task_group cuando tenga varias tareas independientes que desee ejecutar al mismo tiempo, pero desee esperar hasta que las tareas finalicen posteriormente. Por ejemplo, puede agregar tareas a un objeto task_group y esperar hasta que las tareas finalicen en otra función o desde otro subproceso.

Los grupos de tareas admiten el concepto de cancelación. La cancelación permite señalar a todas las tareas activas que desea cancelar la operación en su conjunto. La cancelación también impide que se inicien las tareas que todavía no se hayan iniciado. Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.

El runtime también proporciona un modelo de control de excepciones que permite producir una excepción desde una tarea y controlar esa excepción cuando se espera hasta que finaliza el grupo de tareas asociado. Para obtener más información sobre este modelo de control de excepciones, vea Control de excepciones en el runtime de simultaneidad.

Comparación entre task_group y structured_task_group

Aunque se recomienda usar task_group o parallel_invoke en lugar de la clase structured_task_group, hay casos donde puede ser preferible usar structured_task_group; por ejemplo, cuando se escribe un algoritmo paralelo que realiza un número de tareas variable o que necesita compatibilidad con la cancelación. En esta sección se explican las diferencias entre las clases task_group y structured_task_group.

La clase task_group es segura para subprocesos. Por tanto, puede agregar tareas a un objeto task_group desde varios subprocesos y esperar o cancelar un objeto task_group desde varios subprocesos. La creación y destrucción de un objeto structured_task_group deben realizarse en el mismo ámbito léxico. Además, todas las operaciones sobre un objeto structured_task_group deben realizarse en el mismo subproceso. La excepción a esta regla son los métodos Concurrency::structured_task_group::cancel y Concurrency::structured_task_group::is_canceling. Una tarea secundaria puede llamar a estos métodos para cancelar el grupo de tareas primario o comprobar la cancelación en cualquier momento.

Puede ejecutar tareas adicionales en un objeto task_group después de llamar al método Concurrency::task_group::wait o Concurrency::task_group::run_and_wait. Por el contrario, no puede ejecutar tareas adicionales en un objeto structured_task_group después de llamar a los métodos Concurrency::structured_task_group::wait o Concurrency:: structured_task_group::run_and_wait.

Puesto que la clase structured_task_group no se sincronizado entre subprocesos, tiene menos sobrecarga de ejecución que la clase task_group. Por tanto, si el problema no necesita que se programe trabajo desde varios subprocesos y no se puede usar el algoritmo parallel_invoke, la clase structured_task_group puede ayudarle a escribir código con mejor rendimiento.

Si usa un objeto structured_task_group dentro de otro objeto structured_task_group, el objeto interno debe finalizar y ser destruido antes de que el objeto externo finalizado. La clase task_group no necesita que los grupos de tareas anidadas finalicen antes de que finalice el grupo externo.

Los grupos de tareas no estructurados y los grupos de tareas estructurados usan los identificadores de tareas de maneras diferentes. Puede pasar funciones de trabajo directamente a un objeto task_group; el objeto task_group creará y administrará el identificador de tareas. La clase structured_task_group necesita administrar un objeto task_handle por cada tarea. Cada objeto task_handle debe seguir siendo válido mientras dure su objeto structured_task_group asociado. Use la función Concurrency::make_task para crear un objeto task_handle, como se muestra en el siguiente ejemplo básico:

// make-task-structure.cpp
// compile with: /EHsc
#include <ppl.h>

using namespace Concurrency;

int wmain()
{
   // Use the make_task function to define several tasks.
   auto task1 = make_task([] { /*TODO: Define the task body.*/ });
   auto task2 = make_task([] { /*TODO: Define the task body.*/ });
   auto task3 = make_task([] { /*TODO: Define the task body.*/ });

   // Create a structured task group and run the tasks concurrently.

   structured_task_group tasks;

   tasks.run(task1);
   tasks.run(task2);
   tasks.run_and_wait(task3);
}

Para administrar los identificadores de tareas para aquellos casos en los que tiene un número variable de tareas, use una rutina de asignación de pila como _malloca o una clase contenedora como std::vector.

Tanto task_group como structured_task_group admiten la cancelación. Para obtener más información sobre la cancelación, vea Cancelación en la biblioteca PPL.

Ejemplo

En el siguiente ejemplo básico se muestra cómo trabajar con grupos de tareas. En este ejemplo se usa el algoritmo parallel_invoke para realizar dos tareas simultáneamente. Cada tarea agrega subtareas a un objeto task_group. Observe que la clase task_group permite que varias tareas le agreguen tareas simultáneamente.

// using-task-groups.cpp
// compile with: /EHsc
#include <ppl.h>
#include <sstream>
#include <iostream>

using namespace Concurrency;
using namespace std;

// Prints a message to the console.
template<typename T>
void print_message(T t)
{
   wstringstream ss;
   ss << L"Message from task: " << t << endl;
   wcout << ss.str(); 
}

int wmain()
{  
   // A task_group object that can be used from multiple threads.
   task_group tasks;

   // Concurrently add several tasks to the task_group object.
   parallel_invoke(
      [&] {
         // Add a few tasks to the task_group object.
         tasks.run([] { print_message(L"Hello"); });
         tasks.run([] { print_message(42); });
      },
      [&] {
         // Add one additional task to the task_group object.
         tasks.run([] { print_message(3.14); });
      }
   );

   // Wait for all tasks to finish.
   tasks.wait();
}

A continuación, se muestra la salida de este ejemplo:

Message from task: Hello
Message from task: 3.14
Message from task: 42

Puesto que el algoritmo parallel_invoke ejecuta las tareas simultáneamente, el orden de los mensajes de salida podría variar.

Para obtener ejemplos completos en los que se muestra cómo usar el algoritmo parallel_invoke, vea Cómo: Usar parallel.invoke para escribir una rutina de ordenación en paralelo y Cómo: Usar parallel.invoke para ejecutar operaciones paralelas. Para obtener un ejemplo completo en el que se usa la clase task_group para implementar características asincrónicas, vea Tutorial: Implementar futuros.

Programación sólida

Asegúrese de que entiende el rol de cancelación y el control de excepciones cuando use grupos de tareas y algoritmos paralelos. Por ejemplo, en un árbol de trabajo paralelo, una tarea que se cancela evita que se ejecuten las tareas secundarias. Esto puede producir problemas si una de las tareas secundarias realiza una operación que es importante para la aplicación, como liberar un recurso. Además, si una tarea secundaria produce una excepción, esa excepción podría propagarse a través de un destructor de objeto y provocar un comportamiento no definido en la aplicación. Para obtener un ejemplo que muestra estos puntos, vea la sección Comprender cómo afectan la cancelación y el control de excepciones a la destrucción de objetos del documento Procedimientos recomendados de la Biblioteca de modelos de procesamiento paralelo. Para obtener más información sobre los modelos de cancelación y control de excepciones en PPL, vea Cancelación en la biblioteca PPL y Control de excepciones en el runtime de simultaneidad.

Temas relacionados

Referencia

task_group (Clase)

parallel_invoke (Función)

structured_task_group (Clase)

Historial de cambios

Fecha

Historial

Motivo

Marzo de 2011

Información agregada sobre el rol de la cancelación y el control de excepciones cuando se usan grupos de tareas y algoritmos paralelos.

Mejora de la información.

Julio de 2010

Contenido reorganizado.

Mejora de la información.

Mayo de 2010

Instrucciones ampliadas.

Mejora de la información.