Intel TBB支持異常與終止(cancellation),當算法中的代碼拋出異常時,會按依次發生:
- 捕獲異常。算法內進一步的異常被忽略。
- 算法終止。掛起的迭代操作不被執行。如果內部存在嵌套的Intel TBB並行,那麼它的取消與否取決於特定實現(下面會提到)
- 算法的所有部分都停止後,會在調用算法的線程(thread)上拋出異常。
步驟3中拋出的異常可能是初始的異常,也可能僅僅是captured_exception類型的摘要。後者常發生在當前的系統中,因爲在線程間傳遞異常需要支持C++的std::exception_ptr機制。隨着編譯器在支持此項特性上的進展,將來的Intel TBB版本可能拋出初始的異常。所以,確保你的代碼可以捕獲兩種異常中的任意異常。
#include "tbb/tbb.h"
#include <vector>
#include <iostream>
using namespace tbb;
using namespace std;
vector<int> Data;
struct Update {
void operator ()(const blocked_range<int>& r) const
{
for (int i = r.begin(); i != r.end(); ++i) Data.at(i) += 1;
}
};
int main()
{
Data.resize(1000);
try
{
parallel_for(blocked_range<int>(0, 2000), Update());
}
catch (captured_exception& ex)
{
cout << "captured_exception: " << ex.what() << endl;
}
catch (out_of_range& ex)
{
cout << "out_of_range: " << ex.what() << endl;
}
return 0;
}
無異常終止
要取消某個算法而不拋出異常,使用表達式 task::self().cancel_group_execution(). 其中的task::self()引用當前線程最靠內的Intel TBB任務。調用cancel_group_execution()取消它的task_group_context中的所以線程(下節會詳細介紹)。如果的確導致了任務終止,此方法會返回 true ,如果task_group_context 已經被取消,就會返回 false。
#include "tbb/tbb.h"
#include <vector>
#include <iostream>
using namespace tbb;
using namespace std;
vector<int> Data;
struct Update {
void operator ()(const blocked_range<int>& r) const
{
for (int i = r.begin(); i != r.end(); ++i) if (i < Data.size())
{
++Data[i];
}
else
{
// Cancel related tasks.
if (task::self().cancel_group_execution())
cout << "Index " << i << " caused cancellation\n";
return;
}
}
};
int main()
{
Data.resize(1000);
parallel_for(blocked_range<int>(0, 2000), Update());
return 0;
}
終止與嵌套算法
目前還沒有討論嵌套算法,並且忽略了 task_group_context 的細節。本節將細述兩者。
Intel TBB的算法的執行需要創建 task 對象,此對象會執行你提供給算法模板的代碼片段。默認這些 task 對象會關聯一個算法創建的 task_group_context。嵌套的Intel TBB算法創建一棵 task_group_context 對象樹。終止一個 task_group_context 將會使其所有的子孫 task_group_context 對象終止。因此,一個算法和被它調用的所有算法可以通過一個請求終止。
異常向上傳播。終止向下傳播。這種對立的相互作用是爲了在異常發生時能幹淨地停止一個嵌套計算。以下面圖形中的書爲例,想象每個節點表示一個算法以及它的 task_group_context:
假設節點C拋出一個異常,並且沒有節點捕獲這個異常。Intel TBB 將此異常往上傳遞,終止下面相關的子樹:
1. 在C節點處理異常:
a. 在C處捕獲異常
b. 終止C處的任務
c. 拋出異常 C->B
2. 在B節點處理異常:
a. 在B處捕獲異常
b. 終止B中的任務,根據向下傳播原則,再終止D中的
c. 拋出異常 B->A
3. 在A節點處理異常
a. 在A處捕獲異常
b. 終止A中的人物,根據向下傳播的原則,終止E,F和G的任務
c. 從A點“向上”拋出異常
只要你的代碼捕獲了異常(別管在哪層),Intel TBB 都不會繼續傳播該異常了。例如,一個異常如果沒有脫離外部的 parallel_for 函數體,就不會導致其他的迭代操作終止。
爲了防止終止操作的向下傳播進入某個算法,在棧上構造一個“孤立”的 task_group_context,並將其顯式傳遞給算法。詳情見下面的代碼(簡潔起見,使用了C++0X的lambda表達式):
#include "tbb/tbb.h"
bool Data[1000][1000];
int main()
{
try
{
parallel_for(0, 1000, 1,
[](int i)
{
task_group_context root(task_group_context::isolated);
parallel_for(0, 1000, 1,
[](int j)
{
Data[i][j] = true;
},
root);
throw "oops";
});
}
catch (...)
{
}
return 0;
}
該例執行兩個並行循環:外部在 i 上循環,內部在 j 上循環。內部的代碼[ task_group_context root ]保護內部的循環,使其不被 i 循環中發生異常後的向下傳播規則影響。當異常傳遞給外部循環時,所有掛起狀態的外部迭代都會終止,但已經開始運行的外部迭代的內層迭代不會。因此,當程序完工後,Data 的每一行都可能不同,取決於它的 i 迭代是否完全執行。但是在同一行,元素都統一爲 false 或者 true , 而不是混合的。
如果去掉創建 root 對象的代碼,將會允許終止操作向下傳播到內層循環中。這樣,Data 的一行中可能會包含 true、false 兩種值