任务组取消
新的Beta1 PPL的其中一个功能就是可以取消正在运行中的任务组。在task_group类型上加入run 和 wait方法是一个新的cancel方法。还有一个相应的is_canceling方法让你可以检查在进程中是否完成取消。task_group_status列举也有一个新的值叫做canceled,让你检查取消是否发生。以下的代码展示了这些新的功能:
- //declare tasks and run tasks
- task_group tg;
- tg.run(([]{printf("consoleWrite0\n");}));
- tg.run(([]{printf("consoleWrite1\n");}));
- //cancel tasks
- tg.cancel();
- //check whether tasks are being cancelled
- bool taskGroupIsCanceling = tg.is_canceling();
- //check on status of task group
- task_group_status status = tg.wait();
- if (status == completed){
- printf("Tasks were completed successfully\n");
- }
- else if (status == canceled){
- printf("Tasks were canceled during task execution\n");
- }
- else{
- printf("An exception occured during task execution\n");
- }
Combinable 类型
达到并行执行编程任务的***利益的有效形式之一是工作在本地拷贝或是子数据上的每一个并行分支被处理,然后当处理结束后,将结果合并。这个形式可以***限度的减少资源争用,而且减少潜在的死锁和当并行线程试图更新相同的内存位置时所发生的数据不一致的问题。
使用这个形式没有那么复杂,但是需要很乏味的为每一个使用进行手动编码。为了简化形式使用,Visual C++ 2010 Beta 1 添加了combinable模版类型。这个模版参数传递到combinable是每个任务将要操作的对象类型。这个类型必须有一个默认的构造器和一个拷贝构造器。通过使用local方法,每个任务访问它自己的combinable管理资源的拷贝。 当所有任务结束后,通过使用combine 或是 combine_each方法,你可以将所有结果合并到一个结果集中。
以下的代码用三个独立的任务添加一些元素给一个矢量,然后用两个合并的方法将结果合并成一个矢量。
- //declare a combinable vector of integers
- combinable< vector< int>> v;
- //add an element to the vector using three separate tasks
- parallel_invoke(
- [&]{ v.local().push_back(1); },
- [&]{ v.local().push_back(2); },
- [&]{ v.local().push_back(3); }
- );
- //merge the task-local copies using combine_each
- vector< int> result1;
- v.combine_each(
- [&](vector< int>& local)
- {
- result1.insert(result1.end(),
- local.begin(), local.end());
- }
- );
- //merge the task-local copies using combine
- vector< int> result2 = v.combine(
- [](vector< int> left, vector< int> right)->vector< int>{
- left.insert(left.end(), right.begin(), right.end());
- return left;
- });
要注意的是在***一行的代码语句中的lambda表达式的明确返回类型声明的使用。在lambda表达式中的两个语句阻止编译器正确推断返回类型,所以需要手动声明。
也可以为没有默认构造器(或是在很多情况下默认构造器的使用是不合适的)的类型使用combinable,通过使用combinable构造器,它是采用了一个发生器功能创建了一些模版参数类型的对象。上面代码的前面几行是使用超载发生器构造器而重新编写的。在这种情况下,发生器功能返回已经包含一个元素的矢量。
- //declare a combinable vector of integers
- inable< vector< int>> v([]{return vector< int>(1, 0);});
同步
Combinable的使用取决于不需要作用在其他任务上的结果的任务。在多重任务的情况下需要分享一个对象,你必须使用更传统的同步策略。PLL本身带有三个同步原语——critical_section, reader_writer_lock 和 event。critical_section锁住内存位置防止没有锁的所有任务的访问。对于内存位置将有很多同时读取者和少数编写者,更优化的reader_writer_lock是可用的,允许多重读取者获取锁并同时访问内存位置。***的原语是event,用于在任务和线程之间发信号。
同步原语在concrt.h标题文件(是PPL的基本标题文件,包含在ppl.h中)中被定义。在concrt.h中定义的大多数类型的目标是图书馆作者,而不是应用程序的开发者,但是任何对深层并行开发感兴趣的人都可以自由的调查和使用恰当的来自暴露在外的类型的功能。
critical_section类型的API是非常简单的;你使用一个blocking lock方法来获取这个锁,non-blocking try_lock会试图去获取这个锁,如果它可用的话,而且unlock会释放一个锁住的critical_section。
reader_writer_lock是有一点点的复杂。它添加了两个方法,lock_read 和try_lock_read,可以支持获取一个reader lock。Unlock方法和critical_section是一样的,而且将释放恰当的基于锁类型的锁。
***的同步原语是event,这是手动复位事件(即:事件保持设置直到用外部代码进行手动复位)。代码可以通过调用实例wait方法(也是支持一个可选的timeout值),等待一个单一事件被设置。当没有timeout被定义的时候,等待的时间是无限的。你可以等待多重事件,通过使用静态wait_for_multiple,它接受C-style的event pointers。wait_for_multiple方法等待一个单一事件或是所有的事件传递到方法调用。以下的代码等待这两个事件被设置:
- event* events[2];
- events[0] = new event();
- events[1] = new event();
- parallel_invoke(
- [&]{ events[0]->set(); },
- [&]{ events[1]->set(); }
- );
- bool waitForAllEvents = true;
- event::wait_for_multiple(events, 2, waitForAllEvents);
当同时执行很多任务的时候,处理状态管理是非常艰巨的工作。PPL提供对状态管理的一种形式的支持,就是每个线程在本地版本的分享对象上进行操作,在程序结束的时候将这些结果合并起来。对于分离状态管理的情况是不恰当的,PPL以critical_section, reader_writer_lock 和 event的形式提供传统的同步原语。
【编辑推荐】