并行容器和对象

并行模式库 (PPL) 包含一些容器和对象,这些容器和对象提供对其元素的线程安全访问。

并发容器提供对最重要的操作的并发安全访问。 这些容器的功能类似于标准模板库 (STL) 所提供的功能。 例如, concurrency::concurrent_vector 类类似于 std::vector 类,除了concurrent_vector类使您可以将追加并行的元素。 如果具有需要对相同容器进行读写访问的并行代码,请使用并发容器。

并发对象同时由组件共享。 某个并行计算并发对象状态的进程所产生的结果与按顺序计算相同状态的另一个进程所产生的结果相同。 Concurrency::combinable 类是并发对象类型的一个示例。 combinable 类允许您先并行执行计算,然后将这些计算结果合并成最终结果。 如果要另外使用某个同步机制(例如互斥体)来同步对共享变量或资源的访问,请使用并发对象。

各节内容

本主题详细描述下列并行容器和对象。

并发容器:

  • concurrent_vector 类

    • concurrent_vector 和 vector 之间的区别

    • 并发安全操作

    • 异常安全性

  • concurrent_queue 类

    • concurrent_queue 和 queue 之间的区别

    • 并发安全操作

    • 迭代器支持

  • 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 类可实现并发安全追加和元素访问操作。 追加操作不会使现有指针或迭代器失效。 迭代器访问和遍历操作也是并发安全操作。

Dd504906.collapse_all(zh-cn,VS.110).gifconcurrent_vector 和 vector 之间的区别

concurrent_vector 类与 vector 类非常相似。 对 concurrent_vector 对象执行的追加操作、元素访问操作以及迭代器访问操作的复杂性与 vector 对象相同。 以下几点阐释了 concurrent_vectorvector 之间的差异:

  • 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_bygrow_to_at_least 方法。 这些方法类似于 resize 方法,只不过它们是并发安全方法。

  • 当您追加到 concurrent_vector 对象或调整其大小时,该对象不会重新定位其元素。 这将使现有指针和迭代器在执行并发操作期间保持有效。

  • 运行时未定义 bool 类型的 concurrent_vector 的专用版本。

Dd504906.collapse_all(zh-cn,VS.110).gif并发安全操作

所有追加到 concurrent_vector 对象、增加其大小或访问 concurrent_vector 对象中的元素的方法都是并发安全方法。 对于此规则,resize 方法是一个例外。

下表显示了常见的并发安全的 concurrent_vector 方法和运算符。

at

end

operator[]

begin

front

push_back

back

grow_by

rbegin

capacity

grow_to_at_least

rend

empty

max_size

size

操作运行时提供的兼容性与 STL,例如, reserve,不是并发性安全。 下表显示了常见的不是并发安全的方法和运算符。

assign

reserve

clear

resize

operator=

shrink_to_fit

修改现有元素的值的操作不是并发安全操作。 使用同步对象(如 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 返回的值发生更改。 迭代器遍历的元素数是不确定的。 因此,此程序在您每次运行它时都会产生不同的结果。

Dd504906.collapse_all(zh-cn,VS.110).gif异常安全性

如果增长或赋值操作引发异常,则 concurrent_vector 对象的状态将变为无效。 除非另外声明,否则处于无效状态的 concurrent_vector 对象的行为不确定。 但是,析构函数总是释放对象分配的内存,即使该对象处于无效状态也是如此。

向量元素的数据类型 _Ty 必须满足以下要求。 否则,concurrent_vector 类的行为不确定。

  • 析构函数不得进行引发。

  • 如果默认构造函数或复制构造函数进行了引发,则不得使用 virtual 关键字声明析构函数,并且该析构函数必须能够用初始化为零的内存正常工作。

Top

concurrent_queue 类

Concurrency::concurrent_queue 类,就像 std::queue 类,允许您访问其前端和后端元素。 concurrent_queue 类可实现并发安全排队和取消排队操作。 concurrent_queue 类还提供了不是并发安全的迭代器支持。

Dd504906.collapse_all(zh-cn,VS.110).gifconcurrent_queue 和 queue 之间的区别

concurrent_queue 类与 queue 类非常相似。 以下几点阐释了 concurrent_queuequeue 之间的差异:

  • concurrent_queue 对象执行的排队和取消排队操作是并发安全操作。

  • concurrent_queue 类提供了不是并发安全的迭代器支持。

  • concurrent_queue 类不提供 frontpop 方法。 concurrent_queue 类可通过定义 try_pop 方法替换这些方法。

  • concurrent_queue 类不提供 back 方法。 因此,您无法引用队列的末尾。

  • concurrent_queue 类提供了 unsafe_size 方法,而不是 size 方法。 unsafe_size 方法不是并发安全方法。

Dd504906.collapse_all(zh-cn,VS.110).gif并发安全操作

所有对 concurrent_queue 对象进行排队或取消排队的方法都是并发安全方法。

下表显示了常见的并发安全的 concurrent_queue 方法和运算符。

empty

push

get_allocator

try_pop

尽管 empty 方法是并发安全方法,但是在 empty 方法返回之前,并发操作可能会导致队列增长或缩短。

下表显示了常见的不是并发安全的方法和运算符。

clear

unsafe_end

unsafe_begin

unsafe_size

Dd504906.collapse_all(zh-cn,VS.110).gif迭代器支持

concurrent_queue 提供了不是并发安全的迭代器。 我们建议您只为调试而使用这些迭代器。

concurrent_queue 迭代器只向前遍历元素。 下表显示了每个迭代器支持的运算符。

运算符

说明

operator++

前进到队列中的下一项。 重载此运算符以提供前递增和后递增语义。

operator*

检索对当前项的引用。

operator->

检索指向当前项的指针。

Top

concurrent_unordered_map 类

Concurrency::concurrent_unordered_map 类是一个容器关联类的就像 std::unordered_map 类,控制不同的长度序列的元素类型的 std::pair < const 关键字、 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要执行一个映射,并减少并行操作,请参阅如何:并行执行映射和减少操作

Dd504906.collapse_all(zh-cn,VS.110).gif之间的差异 concurrent_unordered_map 和 unordered_map

concurrent_unordered_map 类与 unordered_map 类非常相似。 以下几点阐释了 concurrent_unordered_mapunordered_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_**命名约定指示这些方法不是并发性安全。 并发性安全有关的详细信息,请参阅并发性安全操作。

  • 插入操作执行使现有的指针或迭代器,也不要更改地图中已存在的项的顺序。 插入和遍历可以同时执行的操作。

  • concurrent_unordered_map支持转发只小版本。

  • 插入不会导致无效或更新迭代器返回的equal_range。 插入区域的末尾可以附加不等项目。 开始迭代器指向相同的项目。

为了避免出现死锁,没有方法的concurrent_unordered_map调用对象的内存分配器、 哈希函数或其他用户定义的代码时保持锁定。 此外,您必须确保哈希函数始终计算为相同的值相等的键。 最佳的哈希函数将键均匀分散的哈希代码空间。

Dd504906.collapse_all(zh-cn,VS.110).gif并发安全操作

concurrent_unordered_map类,并发性安全的插入和元素访问操作。 插入操作使现有的指针或迭代器。 迭代器访问和遍历操作也是并发安全操作。 下表显示了常用concurrent_unordered_map方法和并发性安全的运算符。

at

count

find

key_eq

begin

empty

get_allocator

max_size

cbegin

end

hash_function

operator[]

cend

equal_range

插入

size

虽然count可以并发运行的线程安全调用方法、 不同的线程可以得到不同的结果,如果新值同时插入到容器。

下表显示了常用的方法并不是并发性安全的运算符。

clear

max_load_factor

rehash

load_factor

operator=

换用

除了这些方法,任何方法的开头**unsafe_**也不是并发性安全。

Top

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

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。 本示例插入范围 [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 类通过以无锁方式提供对共享资源的访问,可以帮助您消除共享的状态。 因此,此类为使用同步机制(例如互斥体)同步多线程中共享数据的访问提供了一种替代方法。

Dd504906.collapse_all(zh-cn,VS.110).gif方法和功能

下表显示了 combinable 类的某些重要方法。 有关所有 combinable 类方法的更多信息,请参见combinable 类

方法

说明

local

检索对与当前线程上下文相关联的局部变量的引用。

clear

combinable 对象中移除所有线程本地变量。

combine

combine_each

使用提供的 combine 函数从所有线程本地计算的集合中生成最终值。

combinable 类是根据最终合并结果进行参数化的模板类。 如果调用默认构造函数,则 _Ty 模板参数类型必须具有默认构造函数和复制构造函数。 如果 _Ty 模板参数类型没有默认构造函数,则调用以初始化函数作为其参数的构造函数的重载版本。

在调用 combinecombine_each 方法之后,您可以在 combinable 对象中存储附加数据。 您还可以多次调用 combinecombine_each 方法。 如果 combinable 对象中的本地值没有发生更改,则每次调用 combinecombine_each 方法时,它们都会产生相同的结果。

Dd504906.collapse_all(zh-cn,VS.110).gif示例

有关如何使用 combinable 类的示例,请参见以下主题:

Top

相关主题

引用

concurrent_vector 类

concurrent_queue 类

concurrent_unordered_map 类

concurrent_unordered_multimap 类

concurrent_unordered_set 类

concurrent_unordered_multiset 类

combinable 类