平行容器和物件

平行模式程式庫(PPL)包含數個容器和物件,可線程安全地存取其元素。

並行 容器 可為最重要的作業提供並行安全存取。 在這裡,同時性安全意味著指標或反覆運算器一律有效。 這不保證元素的初始化或特定的遍歷順序。 這些容器的功能類似於 C++ 標準連結庫所提供的容器。 例如, 並行::concurrent_vector 類別類似於 std::vector 類別,不同之處在於類別 concurrent_vector 可讓您平行附加元素。 當您有需要相同容器讀取和寫入存取權的平行程序代碼時,請使用並行容器。

並行物件會在元件之間同時共用。 以平行方式計算並行物件狀態的進程,會產生與另一個以序列方式計算相同狀態的進程相同的結果。 並行::combinable 類別是並行物件類型的其中一個範例。 類別 combinable 可讓您平行執行計算,然後將這些計算合併為最終結果。 當您使用同步處理機制,例如 Mutex 來同步存取共用變數或資源時,請使用並行物件。

區段

本主題詳細說明下列平行容器和物件。

並行容器:

並行物件:

concurrent_vector 類別

concurrency::concurrent_vector 類別是序列容器類別,就像 std::vector 類別一樣,可讓您隨機存取其元素。 類別 concurrent_vector 會啟用並行安全附加和元素存取作業。 附加作業不會使現有的指標或反覆運算器失效。 改良後的翻譯:迭代器的存取和遍歷操作也是並行安全的。 在這裡,同時性安全意味著指標或反覆運算器一律有效。 這不保證元素的初始化或特定的遍歷順序。

concurrent_vector與vector之間的差異

concurrent_vector 類別與 vector 類別非常類似。 物件上的 concurrent_vector 添加、元素存取和迭代器存取作業的複雜度與 vector 物件相同。 下列幾點說明concurrent_vector與vector的不同之處:

  • 新增、元素存取、迭代器存取,以及對 concurrent_vector 物件的迭代器遍歷作業都是並行安全的。

  • 您只能將元素新增至 物件的結尾 concurrent_vector 。 類別 concurrent_vector 不提供 insert 方法。

  • concurrent_vector當您附加至物件時,物件不會使用移動語意。

  • 類別 concurrent_vector 不提供 erase 或 pop_back 方法。 請像使用 vector 一樣,用 clear 方法移除 concurrent_vector 物件中的所有元素。

  • 類別 concurrent_vector 不會將其元素連續儲存在記憶體中。 因此,您無法像使用陣列那樣使用concurrent_vector類別。 例如,對於類型v為的concurrent_vector變數,表達式&v[0]+2會產生未定義的行為。

  • 類別 concurrent_vector 會 定義grow_by 和 grow_to_at_least 方法。 這些方法類似於 resize 方法,不同之處在於它們是並行安全的方法。

  • concurrent_vector當您附加至對象或調整其大小時,物件不會重新放置其元素。 這可讓現有的指標和反覆運算器在並行作業期間保持有效。

  • 執行階段不存在一個針對型別bool的特製化版本concurrent_vector。

並發安全操作

附加至或增加 concurrent_vector 物件大小或存取 concurrent_vector 物件中的元素的所有方法都是並行安全的。 在這裡,同時性安全意味著指標或反覆運算器一律有效。 這不保證元素的初始化或特定的遍歷順序。 這個規則的例外狀況是 resize 方法。

下表顯示並行安全通用 concurrent_vector 方法和運算元。

執行期提供與 C++ 標準程式庫相容的作業,例如,reserve 不是並發安全。 下表顯示非並行安全通用方法和運算符。

修改現有元素值的作業不是執行緒安全的。 使用同步處理物件,例如 reader_writer_lock 物件,將並行讀取和寫入作業同步處理至相同的數據元素。 如需同步處理對象的詳細資訊,請參閱 同步處理數據結構。

當您轉換使用 vectorconcurrent_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 返回的值發生變化。 反覆運算器周游的項目數目不確定。 因此,每次執行此程式時,都可能會產生不同的結果。 當元素類型非平凡時,push_back 和 end 呼叫之間可能會存在競態條件。 end方法可能會傳回已配置但未完全初始化的元素。

例外安全性

如果成長或指派作業擲回例外狀況,物件的狀態 concurrent_vector 就會變成無效。 除非另有說明,否則處於無效狀態的對象行為 concurrent_vector 未定義。 即便物件處於無效狀態,解構子依然會釋放物件所配置的記憶體。

向量項目的 T數據類型必須符合下列需求。 否則,類別的行為 concurrent_vector 是未定義的。

  • 解構函式不得擲回。

  • 如果預設或複製建構函式擲回,則解構函式不得使用 virtual 關鍵詞來宣告,而且必須使用零初始化的記憶體正確運作。

[頂部]

concurrent_queue 類別

concurrency::concurrent_queue 類別,就像 std::queue 類別一樣,可讓您存取其前後元素。 類別concurrent_queue可進行執行緒安全的入列和出列操作。 在這裡,同時性安全意味著指標或反覆運算器一律有效。 這不保證元素的初始化或特定的遍歷順序。 類別 concurrent_queue 也提供非並行安全之反覆運算器支援。

concurrent_queue和佇列之間的差異

concurrent_queue 類別與 queue 類別非常類似。 下列幾點說明concurrent_queue與queue的不同之處:

  • 在concurrent_queue 物件上的入列和出列作業是並發安全的。

  • 類別 concurrent_queue 提供非並行安全之反覆運算器支援。

  • 類別 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提供非並行安全的反覆運算器。 建議您只使用這些疊代器進行偵錯。

concurrent_queue迭代器只能從前到後遍歷元素。 下表顯示每個反覆運算器支援的運算元。

操作員 描述
operator++ 前進到佇列中的下一個項目。 此運算符會被重載,以提供前置與後置遞增的語義。
operator* 取得對目前項目的引用。
operator-> 擷取目前項目的指針。

[頂部]

concurrent_unordered_map 類別

concurrency::concurrent_unordered_map 類別是一種關聯容器類別,就像 std::unordered_map 類別一樣,用於管理不同長度的 std::pair<const Key, Ty> 型元素序列。 將無序映射視為字典,您可以新增一組鍵-值對或按鍵查閱對應的值。 當您有多個線程或工作必須同時存取共用容器、將其插入或更新時,這個類別很有用。

下列範例顯示使用 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 以平行方式執行對應和縮減作業的範例,請參閱 如何:以平行方式執行對應和縮減作業。

concurrent_unordered_map與unordered_map之間的差異

concurrent_unordered_map 類別與 unordered_map 類別非常類似。 下列幾點說明concurrent_unordered_map與unordered_map的不同之處:

  • 方法erase、bucket、bucket_count 和 bucket_size 分別命名為unsafe_erase、unsafe_bucket、unsafe_bucket_count 和unsafe_bucket_size。 unsafe_命名慣例表示這些方法不是並行安全的方法。 如需並行安全性的詳細資訊,請參閱 並行安全作業。

  • 插入操作不會使現有的指標或迭代器失效,也不會改變映射中已經存在的項目順序。 插入和遍歷作業可以同時進行。

  • concurrent_unordered_map 僅支援向前反覆運算。

  • 插入不會使由 equal_range 返回的迭代器失效或更新。 插入可以將不相等的專案附加至範圍的結尾。 起始迭代器指向相等項目。

為了協助避免死結,concurrent_unordered_map 的方法在呼叫記憶體配置器、雜湊函式或其他使用者定義程式代碼時不會持有鎖定。 此外,您必須確保哈希函式會對相同的鍵賦予相同的值。 最佳哈希函式會將索引鍵統一分散到哈希碼空間。

並發安全操作

類別 concurrent_unordered_map 會啟用並行安全插入和元素存取作業。 插入作業不會使現有的指標或反覆運算器失效。 改良後的翻譯:迭代器的存取和遍歷操作也是並行安全的。 在這裡,同時性安全意味著指標或反覆運算器一律有效。 這不保證元素的初始化或特定的遍歷順序。 下表顯示常用且執行緒安全的 concurrent_unordered_map 方法和運算符。

count雖然可以從並行執行的線程安全地呼叫 方法,但如果同時將新的值插入容器中,不同的線程可能會接收不同的結果。

下表顯示非並行安全常用的方法和運算符。

除了這些方法之外,以 開頭 unsafe_ 的任何方法也不是並行安全的方法。

[頂部]

concurrent_unordered_multimap 類別

concurrency::concurrent_unordered_multimap 類別與 concurrent_unordered_map 類別非常類似,不同之處在於它允許多個值對應至相同的鍵。 其也與 concurrent_unordered_map 下列方式不同:

  • concurrent_unordered_multimap::insert 方法會傳回一個反覆運算器,而非 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]
*/

[頂部]

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]
*/

[頂部]

concurrent_unordered_multiset 類別

concurrency::concurrent_unordered_multiset 類別與concurrent_unordered_set類別非常相似,不同之處在於它允許重複的值。 其也與 concurrent_unordered_set 下列方式不同:

下列範例顯示使用 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]
*/

[頂部]

可組合的類

concurrency::combinable 類別提供可重複使用的線程本機記憶體,可讓您執行精細的計算,然後將這些計算合併至最終結果。 您可以將 combinable 物件視為削減變數。

當您有數個線程或工作之間共用的資源時,類別 combinable 會很有用。 類別 combinable 可協助您排除共享狀態,方法是以無鎖定方式提供共用資源的存取權。 因此,這個類別提供使用同步處理機制的替代方法,例如 Mutex,以同步存取多個線程的共享數據。

方法和功能

下表顯示 類別的 combinable 一些重要方法。 如需所有 combinable 類別方法的詳細資訊,請參閱 可合併類別。

方法 描述
本機 擷取與目前線程內容相關聯的局部變數參考。
清除 從物件中移除所有線程局部變數 combinable 。
結合

combine_each
使用提供的 combine 函式,從所有線程區域計算集產生最終值。

類別 combinable 是範本類別,會在最終合併的結果上參數化。 如果您呼叫預設建構函式, T 範本參數類型必須具有預設建構函式和複製建構函式。 T如果範本參數類型沒有預設建構函式,請呼叫採用初始化函式做為其參數的建構函式多載版本。

呼叫 combine 或 combine_each 方法之後,您可以將其他數據儲存在 combinable 物件中。 您也可以多次呼叫 combine 和 combine_each 方法。 如果對象中 combinable 沒有任何本機值變更, combine 則 和 combine_each 方法會在每次呼叫時產生相同的結果。

範例

如需如何使用 類別的 combinable 範例,請參閱下列主題:

  • 如何使用 combinable 提升效能

  • 如何:使用「可合併」功能來結合集合

[頂部]

如何:使用平行容器提高效率
示範如何使用平行容器,以平行方式儲存和存取數據。

如何使用 combinable 提升效能
示範如何使用 combinable 類別來消除共享狀態,進而改善效能。

如何:使用「可合併」功能來結合集合
示範如何使用 函 combine 式來合併線程區域數據集。

平行模式程式庫 (PPL)
描述 PPL,其提供命令式程式設計模型,可促進可擴縮性和方便使用,以開發並行應用程式。

參考

concurrent_vector 類別

concurrent_queue 類別

concurrent_unordered_map 類別

concurrent_unordered_multimap 類別

concurrent_unordered_set 類別

concurrent_unordered_multiset 類別

可組合類別