windows C++-并行编程-PPL任务并行(三)

when_any 函数

when_any 函数生成在任务集中的第一个任务完成时完成的任务。 此函数返回 std::pair 对象,其中包含已完成任务的结果和该任务在集中的索引。

when_any 函数在以下情境中尤其有用:

  • 冗余运算。 请考虑可以用多种方式执行的算法或运算。 你可使用 when_any 函数来选择先完成的运算,然后取消剩余的运算;
  • 交叉运算。 你可启动必须全部完成的多项运算,并使用 when_any 函数在每项运算完成时处理结果。 在一项运算完成后,可以启动一个或多个其他任务;
  • 受限制的运算。 你可使用 when_any 函数通过限制并发运算的数量来扩展前面的情境;
  • 过期的运算。 你可使用 when_any 函数在一个或多个任务与特定时间后完成的任务间进行选择;

与 when_all 一样,可使用具有 when_any 的延续以在任务集中的第一个任务完成时执行操作,这十分常见。 下面的基本示例使用 when_any 创建一个在三个其他任务中的第一个任务完成时完成的任务。

// select-task.cpp
// compile with: /EHsc
#include <ppltasks.h>
#include <array>
#include <iostream>

using namespace concurrency;
using namespace std;

int wmain()
{
    // Start multiple tasks.
    array<task<int>, 3> tasks = {
        create_task([]() -> int { return 88; }),
        create_task([]() -> int { return 42; }),
        create_task([]() -> int { return 99; })
    };

    // Select the first to finish.
    when_any(begin(tasks), end(tasks)).then([](pair<int, size_t> result)
    {
        wcout << "First task to finish returns "
              << result.first
              << L" and has index "
              << result.second
              << L'.' << endl;
    }).wait();
}

/* Sample output:
    First task to finish returns 42 and has index 1.
*/

在此示例中,还可以指定 task<pair<int, size_t>> 以生成基于任务的延续。

与 when_all 一样,传递给 when_any 的任务必须返回相同类型。

还可以使用 || 语法生成在任务集中的第一个任务完成之后完成的任务,如下面的示例所示。

auto t = t1 || t2; // same as when_any

与 when_all 一样,when_any 是非阻止的,可以安全地在 UWP 应用中在 ASTA 线程上进行调用。

延迟的任务执行

有时需要延迟任务的执行,直到满足条件,或开始一项任务来响应外部事件。 例如,在异步编程中,可能必须启动任务以响应 I/O 完成事件。

实现此目的的两种方法是使用延续,或启动任务并在任务的工作函数内等待某个事件。 但是在某些情况下,无法使用以上方法之一。 例如,若要创建延续,必须具有先行任务。 但是,如果没有先行任务,则可以创建任务完成事件,稍后在先行任务可用时将该完成事件链接到先行任务。 此外,因为正在等待的任务也会阻止线程,所以可以使用任务完成事件在异步操作完成时执行工作,从而释放线程。

concurrency::task_completion_event 类可帮助简化这种任务组合。 与 task 类一样,类型参数 T 是由任务生成的结果的类型。 如果任务不返回值,则此类型可以是 void。 T 不能使用 const 修饰符。 通常会将 task_completion_event 对象提供给在它的值可用时向它发信号的线程或任务。 同时,一个或多个任务设置为该事件的侦听器。 设置事件时,侦听器任务完成,其延续会计划运行。

任务组

任务组可组织任务的集合。 任务组会将任务推送到工作窃取队列。 计划程序从此队列中删除任务,然后在可用计算资源上执行它们。 将任务添加到任务组之后,可以等待所有任务完成或取消尚未开始的任务。

PPL 使用 concurrency::task_group 和 concurrency::structured_task_group 类表示任务组,使用 concurrency::task_handle 类表示在这些组中运行的任务。 task_handle 类封装执行工作的代码。 与 task 类一样,工作函数采用 lambda 函数、函数指针或函数对象的形式。 通常不需要直接使用 task_handle 对象。 而是将工作函数传递给任务组,然后任务组会创建和管理 task_handle 对象。

PPL 将任务组划分为这两个类别:非结构化任务组和结构化任务组。 PPL 使用 task_group 类表示非结构化任务组,使用 structured_task_group 类表示结构化任务组。

PL 还定义了 concurrency::parallel_invoke 算法,该算法使用 structured_task_group 类并行执行任务集。 因为 parallel_invoke 算法具有更简洁的语法,所以我们建议尽可能使用它而不是 structured_task_group 类。 

当你具有要同时执行的多个独立任务,并且你必须等待所有任务完成才能继续时,可使用 parallel_invoke。 此方法通常称为分叉和联接并行。 当你具有要同时执行的多个独立任务,但是要在以后等待任务完成时,可使用 task_group。 例如,可以将任务添加到 task_group 对象,并在另一个函数中或从另一个线程等待任务完成。

任务组支持取消概念。 取消使你可以向所有活动任务发出信号,表示你要取消整个操作。 取消还可以阻止尚未开始的任务开始。 有关取消的详细信息,请参阅 PPL 中的取消操作。

运行时还提供了一个异常处理模型,使你可以从任务引发异常,并在等待关联任务组完成时处理该异常。

比较 task_group 与 structured_task_group

虽然我们建议你使用 task_group 或 parallel_invoke 而不是 structured_task_group 类,不过有一些你要使用 structured_task_group 的情况,例如当你编写执行可变数量的任务或需要取消支持的并行算法时。 此部分介绍 task_group 与 structured_task_group 类之间的差异。

task_group 类是线程安全的。 因此,可以从多个线程将任务添加到 task_group 对象,然后从多个线程等待或取消 task_group 对象。 structured_task_group 对象的构造和析构必须在相同词法范围内进行。 此外,对 structured_task_group 对象执行的所有操作必须在相同线程上进行。 此规则的例外是 concurrency::structured_task_group::cancel 和 concurrency::structured_task_group::is_canceling 方法。 子任务可以随时调用这些方法以取消父任务组或检查是否存在取消。

可以在调用 concurrency::task_group::wait 或 concurrency::task_group::run_and_wait 方法之后对 task_group 对象运行其他任务。 相反,如果在调用 concurrency::structured_task_group::wait 或 concurrency::structured_task_group::run_and_wait 方法之后对 structured_task_group 对象运行其他任务,则行为不明确。

因为 structured_task_group 类不会在线程间同步,所以它的执行开销比 task_group 类更少。 因此,如果你的问题不需要从多个线程计划工作并且你无法使用 parallel_invoke 算法,则 structured_task_group 类可帮助你编写更好的执行代码。

如果在另一个 structured_task_group 对象内使用一个 structured_task_group 对象,则内部对象必须在外部对象完成之前完成并销毁。 task_group 类不要求嵌套任务组在外部组完成之前完成。

非结构化任务组和结构化任务组以不同方式处理任务句柄。 可以将工作函数直接传递给 task_group 对象;task_group 对象会为你创建并管理任务句柄。 structured_task_group 类要求你为每个任务管理 task_handle 对象。 每个 task_handle 对象必须在其关联 structured_task_group 对象的整个生存期内保持有效。 使用 concurrency::make_task 函数可创建 task_handle 对象,如下面的基本示例所示:

// 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);
}

若要在具有可变数量任务的情况下管理任务句柄,请使用堆栈分配例程(如 _malloca)或容器类(如 std::vector)。

task_group 和 structured_task_group 都支持取消。

示例

下面的基本示例演示如何使用任务组。 此示例使用 parallel_invoke 算法并发执行两个任务。 每个任务都将子任务添加到一个 task_group 对象。 请注意,task_group 类允许多个任务同时向它添加任务。

// 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();
}

以下是此示例的示例输出:

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

因为 parallel_invoke 算法并发运行任务,所以输出消息的顺序可能会有所不同。

可靠编程

请确保了解取消和异常处理在使用任务、任务组和并行算法时的角色。 例如,在并行工作树中,取消的任务会阻止子任务运行。 如果一个子任务执行的操作对于应用程序很重要(如释放资源),则这可能会导致问题。 此外,如果子任务引发异常,则该异常可以通过对象析构函数进行传播,在应用程序中导致不明确的行为。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值