让我扩展我的评论。除非您以跨平台兼容性为目标,并且您希望您的代码也可以编译和使用 MS Visual C++,否则您可以通过使用显式 OpenMP 任务来抵消为图形对象提供“线性”迭代器的复杂性。 OpenMP 3.0 中引入了显式任务(因此 MSVC 不支持它,它只符合更早的规范,即使在 2012 年也是如此)。任务是可以并行执行的代码块。它们由task 构造创建:
... other code ...
#pragma omp task
{
... task code ...
}
... other code ...
每当执行流程到达标记区域时,就会创建一个新的任务对象并将其放入任务队列中。然后在某个时间点,空闲线程从队列中抓取一个任务并开始执行它。任务与 OpenMP 部分非常相似,因为它们继承了它们的环境,并且它们可以以与代码的串行版本不同的顺序运行。
通过任务可以实现递归算法,也可以轻松使用不提供随机迭代器的 C++ 容器。例如,二叉树的遍历可以像这样使用任务执行:
// Helper routine to traverse a tree and create one task for each node
void traverse_and_make_tasks(tree_node *tn)
{
if (tn == NULL)
return;
// Create a task to process the node
#pragma omp task
{
very_long_computation(tn->value);
}
traverse_and_make_tasks(tn->left);
traverse_and_make_tasks(tn->right);
}
... main code ...
// Disable dynamic teams
omp_set_dynamic(0);
#pragma omp parallel
{
#pragma omp single nowait
{
traverse_and_make_tasks(tree->root_node);
}
}
(需要禁用动态团队以防止 OpenMP 运行时过于智能并单线程执行 parallel 区域)
这是一种非常常见的任务模式,称为单一/串行生产者。每当执行进入parallel 区域时,单个线程就会执行single 构造中的代码。它用三个的根节点调用traverse_and_make_tasks。 traverse_and_make_tasks 遍历三个并为每个节点创建一个任务。 task 构造仅在parallel 区域内使用(静态范围)或在从parallel 区域内(动态范围)调用(直接或间接)的代码中使用时才有效。当traverse_and_make_tasks 遍历树时,它会产生排队的任务。在parallel 区域的末尾有一个隐式的任务调度点,这大致意味着在所有任务都执行完之前,执行不会超过区域的末尾。也可以使用#pragma omp taskwait 在平行区域内放置显式点。它的工作方式类似于barrier - 执行“阻塞”,直到所有任务都被处理完。
另一个常见的模式是并行生成任务的并行生产者。通过对traverse_and_make_tasks的简单修改,上面的示例代码可以很容易地转换为并行生产者:
void traverse_and_make_tasks(tree_node *tn)
{
if (tn == NULL)
return;
#pragma omp task
traverse_and_make_tasks(tn->left);
#pragma omp task
traverse_and_make_tasks(tn->right);
// Create a task to process the node
very_long_computation(tn->value);
}
此版本的代码在每个节点创建两个任务 - 一个处理左后代,另一个处理右后代。如果这是串行代码,它将自下而上遍历树。然而,在并行情况下,任务排队或多或少会导致从上到下的遍历。
还有许多其他可能的使用任务的场景。也可以在非递归情况下使用它们来处理不提供随机迭代器的容器,这是工作共享构造 for 所要求的:
typedef container_type::iterator citer;
container_type container;
... push some values in the container ...
#pragma omp parallel
{
#pragma omp single nowait
{
for (citer it = container.begin(); it != container.end(); it++)
#pragma omp task
process(*it);
}
#pragma omp taskwait
// process more
#pragma omp single nowait
{
for (citer it = container.begin(); it != container.end(); it++)
#pragma omp task
process_more(*it);
}
}
此示例还说明了在 parallel 区域内使用显式任务同步。