Share via


平行容器和物件

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

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

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

區段

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

並行容器:

並行物件:

concurrent_vector 類別

concurrency::concurrent_vector 類別是序列容器類別,就像 std::vector 類別一樣 ,可讓您隨機存取其元素。 類別 concurrent_vector 會啟用並行安全附加和元素存取作業。 附加作業不會使現有的指標或反覆運算器失效。 反覆運算器存取和周遊作業也是並行安全。 在這裡,並行安全表示指標或反覆運算器一律有效。 這不是元素初始化或特定周遊順序的保證。

concurrent_vector與向量之間的差異

類別與 類別 concurrent_vector 非常類似 vector 。 物件上的 concurrent_vector 附加、專案存取和反覆運算器存取作業的複雜度與 vector 物件相同。 下列幾點說明與 的不同 vector 之處 concurrent_vector

  • 附加、元素存取、反覆運算器存取,以及物件上的 concurrent_vector 反覆運算器周遊作業都是並行安全。

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

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

  • 類別 concurrent_vector 不提供 erasepop_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當您附加至物件或調整其大小時,物件不會重新放置其元素。 這可讓現有的指標和反覆運算器在並行作業期間保持有效。

  • 執行時間不會針對 型別 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_backend 可能會有競爭條件。 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 。 下列幾點說明與 的不同 queue 之處 concurrent_queue

  • 物件上的 concurrent_queue 排入佇列和清除佇列作業是並行安全。

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

  • 類別 concurrent_queue 不提供 frontpop 方法。 類別 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::p air < 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 。 下列幾點說明與 的不同 unordered_map 之處 concurrent_unordered_map

  • 、、 和 方法分別命名為 unsafe_eraseunsafe_bucketunsafe_bucket_count 、 和 unsafe_bucket_sizebucket_sizebucket_countbucketerase 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 的基本結構。 本範例會在範圍 ['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]
*/

[靠上]

combinable 類別

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

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

方法和功能

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

方法 描述
local 擷取與目前線程內容相關聯的區域變數參考。
clear 從 物件中移除所有線程區域變數 combinable
combine

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

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

呼叫 combine 或 combine_each 方法之後 ,您可以將其他資料儲存在 combinable 物件 中。 您也可以多次呼叫 combinecombine_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 類別

combinable 類別