平行容器和物件
平行模式程式庫 (PPL) 包含數個容器和物件,這些容器和物件提供對其項目的執行緒安全存取。
「並行容器」(Concurrent Container) 提供對最重要作業的並行安全存取。這些容器的功能與標準範本程式庫 (STL) 所提供的容器類似。例如, concurrency::concurrent_vector 類別類似於 std::vector 類別,除了, concurrent_vector類別可讓您新增項目以平行方式。當您的平行程式碼需要同時對相同容器進行讀取和寫入存取時,請使用並行容器。
「並行物件」(Concurrent Object) 是各元件並行共用的物件。以平行方式計算並行物件的狀態,會產生與以循序方式計算該並行物件的狀態相同的結果。Concurrency::combinable 類別是一個例子為並行處理的物件型別。combinable 類別可讓您平行執行計算,然後再將這些計算結合成最終的結果。並行物件可以做為替代方案,來替代使用同步處理機制 (例如 Mutex) 同步處理對共用變數或資源的存取。
章節
本主題詳述下列平行容器和物件。
並行容器:
concurrent_vector 類別
concurrent_vector 與 vector 之間的差異
並行安全的作業
例外狀況安全
concurrent_queue 類別
concurrent_queue 與 queue 之間的差異
並行安全的作業
Iterator 支援
concurrent_unordered_map 類別
差異之間 concurrent_unordered_map 和 unordered_map
並行安全的作業
concurrent_unordered_multimap 類別
concurrent_unordered_set 類別
concurrent_unordered_multiset 類別
並行物件:
combinable 類別
方法和功能
範例
concurrent_vector 類別
Concurrency::concurrent_vector 類別是一種序列容器類別,就像 std::vector 類別,可讓您在隨機存取其項目。concurrent_vector 類別啟用並行安全的附加和項目存取作業。附加作業並不會讓現有指標或 Iterator 失效。Iterator 存取和周遊作業也是並行安全的。
concurrent_vector 與 vector 之間的差異
concurrent_vector 類別與 vector 類別類似。concurrent_vector 物件上附加、項目存取和 Iterator 存取作業的複雜度與 vector 物件是相同的。下列各點說明 concurrent_vector 與 vector 的差異:
concurrent_vector 物件上的附加、項目存取、Iterator 存取和 Iterator 周遊作業是並行安全的。
您只能將項目加至 concurrent_vector 物件的結尾。concurrent_vector 類別未提供 insert 方法。
當您在 concurrent_vector 物件中附加項目時,這個物件並不會使用移動語意。
concurrent_vector 類別未提供 erase 或 pop_back 方法。如同 vector,請使用 clear 方法移除 concurrent_vector 物件中的所有項目。
concurrent_vector 類別不會將自己的項目連續儲存在記憶體中。因此,您無法完全像使用陣列一樣地使用 concurrent_vector 類別。例如,針對型別為 concurrent_vector 的變數 v,運算式 &v[0]+2 會產生未定義的行為。
concurrent_vector 類別會定義 grow_by 和 grow_to_at_least 方法。這些方法與 resize 方法類似,不同之處在於它們是並行安全的。
當您在 concurrent_vector 物件中附加項目或變更物件的大小時,這個物件並不會更動其內項目的位置。這可讓現有指標和 Iterator 在並行作業期間一直保持有效。
執行階段不會針對 bool 型別定義專門的 concurrent_vector 版本。
並行安全的作業
所有方法在 concurrent_vector 物件中附加項目或是增加物件大小,或者是存取 concurrent_vector 物件中的項目時,都是並行安全的。這項規則的例外是 resize 方法。
下表顯示常見並行安全的 concurrent_vector 方法和運算子。
作業執行階段提供的相容性 STL,比方說, reserve,不是安全的並行存取。下表顯示常見不是並行安全的方法和運算子。
修改現有項目值的作業並不是並行安全的。若要同步處理對相同資料項目的並行讀取和寫入作業,請使用同步處理物件 (例如 reader_writer_lock 物件)。如需同步處理物件的詳細資訊,請參閱同步處理資料結構。
當您將使用 vector 的現有程式碼轉換為使用 concurrent_vector 時,並行作業可能會變更您應用程式的行為。例如,下列範例會在 concurrent_vector 物件上並行執行兩個工作。第一個工作會將其他項目附加至 concurrent_vector 物件。第二個工作會計算同一個物件中所有項目的總和。
// parallel-vector-sum.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_vector.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
// Create a concurrent_vector object that contains a few
// initial elements.
concurrent_vector<int> v;
v.push_back(2);
v.push_back(3);
v.push_back(4);
// Perform two tasks in parallel.
// The first task appends additional elements to the concurrent_vector object.
// The second task computes the sum of all elements in the same object.
parallel_invoke(
[&v] {
for(int i = 0; i < 10000; ++i)
{
v.push_back(i);
}
},
[&v] {
combinable<int> sums;
for(auto i = begin(v); i != end(v); ++i)
{
sums.local() += *i;
}
wcout << L"sum = " << sums.combine(plus<int>()) << endl;
}
);
}
雖然 end 方法是並行安全的,但是並行呼叫 push_back 方法會變更 end 所傳回的值。Iterator 會周遊的項目數是不一定的。因此,每次執行時這個程式都會產生不同的結果。
例外狀況安全
如果成長或指派作業擲回例外狀況,則 concurrent_vector 物件的狀態會變無效。除非另有陳述,否則處於無效狀態之 concurrent_vector 物件的行為會是未定義的行為。不過,解構函式一定會釋出物件所配置的記憶體,即使物件處於無效狀態也是一樣。
vector 項目的資料型別 _Ty 必須符合下列需求。否則,concurrent_vector 類別的行為會是未定義的行為。
解構函式不得擲回例外狀況。
如果預設或複製建構函式擲回例外狀況,則不得以 virtual 關鍵字宣告解構函式,而且解構函式必須能夠與以零初始化的記憶體正常互動。
Top
concurrent_queue 類別
Concurrency::concurrent_queue 類別,就像是 std::queue 類別,可讓您取得其最上層,並傳回項目。concurrent_queue 類別啟用並行安全的加入佇列和清除佇列作業。concurrent_queue 類別也提供不是並行安全的 Iterator 支援。
concurrent_queue 與 queue 之間的差異
concurrent_queue 類別與 queue 類別類似。下列各點說明 concurrent_queue 與 queue 的差異:
concurrent_queue 物件上的加入佇列和清除佇列作業是並行安全的。
concurrent_queue 類別則提供不是並行安全的 Iterator 支援。
concurrent_queue 類別未提供 front 或 pop 方法。concurrent_queue 類別會定義 try_pop 方法,藉以取代這些方法。
concurrent_queue 類別未提供 back 方法。因此,您無法參考佇列的結尾。
concurrent_queue 類別提供 unsafe_size 方法,而非 size 方法。unsafe_size 方法不是並行安全的。
並行安全的作業
所有在 concurrent_queue 物件中加入佇入或清除佇列的方法都是並行安全的。
下表顯示常見並行安全的 concurrent_queue 方法和運算子。
雖然 empty 方法是並行安全的,但是並行作業可能會造成佇列在 empty 方法傳回之前就變大或變小。
下表顯示常見不是並行安全的方法和運算子。
Iterator 支援
concurrent_queue 提供不是並行安全的 Iterator。建議您只將這些 Iterator 用來進行偵錯。
concurrent_queue Iterator 只會以前進方向周遊項目。下表顯示每個 Iterator 所支援的運算子。
運算子 |
描述 |
---|---|
前進至佇列中的下一個項目。這個運算子使用多載,來提供遞增前和遞增後的語意。 |
|
擷取目前項目的參考。 |
|
擷取目前項目的指標。 |
Top
concurrent_unordered_map 類別
Concurrency::concurrent_unordered_map 類別是關聯的容器類別,就像 std::unordered_map 類別、 使不同長度序列型別的項目會控制 std::pair < const 機碼、 智 >。您可以將未按順序的地圖想像成字典,您可以加入索引鍵 / 值組,或依據索引鍵查詢特定值。當您有多重執行緒或所需同時存取共用的容器、 中、 插入或更新該工作時,這個類別是很有用。
下列範例顯示使用的基本結構concurrent_unordered_map。本範例插入 ['a',' i'] 範圍中的字元按鍵。因為某種無法判別的運算順序,每個索引鍵的最終值也是某種無法判別的。不過,則以平行方式執行插入。
// unordered-map-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the map in parallel.
concurrent_unordered_map<char, int> map;
parallel_for(0, 1000, [&map](int i) {
char key = 'a' + (i%9); // Geneate a key in the range [a,i].
int value = i; // Set the value to i.
map.insert(make_pair(key, value));
});
// Print the elements in the map.
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
/* Sample output:
[e, 751] [i, 755] [a, 756] [c, 758] [g, 753] [f, 752] [b, 757] [d, 750] [h, 754]
*/
如需範例來使用concurrent_unordered_map執行地圖及減少平行地操作,請參閱HOW TO:平行執行對應和縮減作業。
差異之間 concurrent_unordered_map 和 unordered_map
concurrent_unordered_map 類別與 unordered_map 類別類似。下列各點說明 concurrent_unordered_map 與 unordered_map 的差異:
The erase, bucket, bucket_count, and bucket_size methods are named unsafe_erase, unsafe_bucket, unsafe_bucket_count, and unsafe_bucket_size, respectively.**unsafe_**命名慣例表示這些方法都不並行存取之。如需有關並行存取安全的詳細資訊,請參閱並行存取之作業。
插入動作顯示現有的指標或 iterator,不使失效,也不要變更已存在於對應的項目順序。插入並瀏覽樹狀作業可以同時發生。
concurrent_unordered_map支援轉寄僅反覆項目。
插入不會使其失效或更新所傳回的 iterator equal_range。插入可以將範圍的結尾附加不相等的項目。開始 iterator 指向相同的項目。
為避免發生死結,方法沒有concurrent_unordered_map持有鎖定時記憶體配置器、 雜湊函式或其他使用者定義的程式碼,它會呼叫。此外,您必須確定雜湊函式一律會評估為相同的值相等的機碼。最佳的雜湊函式統一散發金鑰雜湊程式碼空間上。
並行安全的作業
concurrent_unordered_map類別可讓並行存取之插入] 和 [項目存取作業。現有的指標或 iterator,並無法確認 insert 的動作。Iterator 存取和周遊作業也是並行安全的。下表顯示常用concurrent_unordered_map方法和並行存取之運算子。
count |
find |
||
begin |
empty |
get_allocator |
max_size |
cbegin |
end |
hash_function |
|
cend |
equal_range |
size |
雖然count可以安全地呼叫方法,從幫助同時執行執行緒、 不同的執行緒可以得到不同的結果加入至容器同時插入新的值。
下表顯示常用的方法並不是安全的並行處理的操作員。
clear |
max_load_factor |
rehash |
load_factor |
除了這些方法的任何方法的開頭是**unsafe_**也不是並行存取之。
Top
concurrent_unordered_multimap 類別
Concurrency::concurrent_unordered_multimap 類別非常類似concurrent_unordered_map類別的不同之處在於它可以讓多個值將對應到相同的金鑰。它也會不同於concurrent_unordered_map以下列方式:
Concurrent_unordered_multimap::insert 方法會傳回而不是 iterator std::pair<iterator, bool>。
concurrent_unordered_multimap類別未提供operator[]或at方法。
下列範例顯示使用的基本結構concurrent_unordered_multimap。本範例插入 ['a',' i'] 範圍中的字元按鍵。concurrent_unordered_multimap可以有多個值的索引鍵。
// unordered-multimap-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_map.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the map in parallel.
concurrent_unordered_multimap<char, int> map;
parallel_for(0, 10, [&map](int i) {
char key = 'a' + (i%9); // Geneate a key in the range [a,i].
int value = i; // Set the value to i.
map.insert(make_pair(key, value));
});
// Print the elements in the map.
for_each(begin(map), end(map), [](const pair<char, int>& pr) {
wcout << L"[" << pr.first << L", " << pr.second << L"] ";
});
}
/* Sample output:
[e, 4] [i, 8] [a, 9] [a, 0] [c, 2] [g, 6] [f, 5] [b, 1] [d, 3] [h, 7]
*/
Top
concurrent_unordered_set 類別
Concurrency::concurrent_unordered_set 類別非常類似concurrent_unordered_map類別的不同之處在於它管理的而不是索引鍵和值組的值。concurrent_unordered_set類別未提供operator[]或at方法。
下列範例顯示使用的基本結構concurrent_unordered_set。本範例插入 ['a',' i'] 範圍中的字元值。則以平行方式執行插入。
// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the set in parallel.
concurrent_unordered_set<char> set;
parallel_for(0, 10000, [&set](int i) {
set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
});
// Print the elements in the set.
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
/* Sample output:
[e] [i] [a] [c] [g] [f] [b] [d] [h]
*/
Top
concurrent_unordered_multiset 類別
Concurrency::concurrent_unordered_multiset 類別非常類似concurrent_unordered_set類別的不同之處在於它允許重複的值。它也會不同於concurrent_unordered_set以下列方式:
Concurrent_unordered_multiset::insert 方法會傳回而不是 iterator std::pair<iterator, bool>。
concurrent_unordered_multiset類別未提供operator[]或at方法。
下列範例顯示使用的基本結構concurrent_unordered_multiset。本範例插入 ['a',' i'] 範圍中的字元值。concurrent_unordered_multiset可以讓發生多次的值。
// unordered-set-structure.cpp
// compile with: /EHsc
#include <ppl.h>
#include <concurrent_unordered_set.h>
#include <iostream>
using namespace concurrency;
using namespace std;
int wmain()
{
//
// Insert a number of items into the set in parallel.
concurrent_unordered_multiset<char> set;
parallel_for(0, 40, [&set](int i) {
set.insert('a' + (i%9)); // Geneate a value in the range [a,i].
});
// Print the elements in the set.
for_each(begin(set), end(set), [](char c) {
wcout << L"[" << c << L"] ";
});
}
/* Sample output:
[e] [e] [e] [e] [i] [i] [i] [i] [a] [a] [a] [a] [a] [c] [c] [c] [c] [c] [g] [g]
[g] [g] [f] [f] [f] [f] [b] [b] [b] [b] [b] [d] [d] [d] [d] [d] [h] [h] [h] [h]
*/
Top
combinable 類別
Concurrency::combinable 類別會提供可重複使用、 執行緒區域儲存區,可讓您執行更細緻的運算,然後將這些計算合併到最終的結果。您可以將 combinable 物件視為削減變數。
當您的資源會供數個執行緒或工作共用時,combinable 類別會很有用。combinable 類別可藉由以無法鎖定的方式提供對共用資源的存取,協助您消除共用狀態。因此,這個類別提供另一種替代方式,來替代使用同步處理機制 (例如 Mutex) 同步處理多個執行緒對共用資料的存取。
方法和功能
下表顯示 combinable 類別的一些重要方法。如需所有 combinable 類別方法的詳細資訊,請參閱 combinable 類別。
方法 |
描述 |
---|---|
擷取與目前執行緒內容相關聯之區域變數的參考。 |
|
移除 combinable 物件中的所有執行緒區域變數。 |
|
使用提供的 combine 函式,從一組全部都是在執行緒區域執行的計算來產生最終的值。 |
combinable 類別是在最終合併結果上參數化的範本類別。如果您呼叫預設建構函式,則 _Ty 範本參數型別必須要有預設建構函式和複製建構函式。如果 _Ty 範本參數型別沒有預設建構函式,請呼叫以初始設定函式為參數的建構函式多載版本。
呼叫 combine 或 combine_each 方法之後,您就可以在 combinable 物件中儲存額外的資料。您也可以多次呼叫 combine 和 combine_each 方法。如果 combinable 物件中的區域數值未變更,則每次呼叫 combine 和 combine_each 方法都會產生相同的結果。
範例
如需如何使用 combinable 類別的範例,請參閱下列主題:
Top
相關主題
HOW TO:使用平行容器提高效率
顯示如何使用平行容器以平行方式有效率地儲存和存取資料。HOW TO:使用可組合的類別改善效能
顯示如何使用 combinable 類別消除共用狀態,進而改善效能。HOW TO:使用可組合的類別結合集合
顯示如何使用 combine 函式來合併執行緒區域資料集。平行模式程式庫 (PPL)
描述 PPL,PPL 提供了重要的程式設計模型來提升開發並行應用程式時的延展性和易用性。
參考資料
concurrent_unordered_multimap 類別