并行容器和对象
并行模式库 (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 类可实现并发安全追加和元素访问操作。 追加操作不会使现有指针或迭代器失效。 迭代器访问和遍历操作也是并发安全操作。
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 类。 例如,对于 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 方法和运算符。
运行时为保持 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 返回的值发生更改。 迭代器遍历的元素数是不确定的。 因此,此程序在您每次运行它时都会产生不同的结果。
异常安全性
如果增长或赋值操作引发异常,则 concurrent_vector 对象的状态将变为无效。 除非另外声明,否则处于无效状态的 concurrent_vector 对象的行为不确定。 但是,析构函数总是释放对象分配的内存,即使该对象处于无效状态也是如此。
向量元素的数据类型 _Ty 必须满足以下要求。 否则,concurrent_vector 类的行为不确定。
析构函数不得进行引发。
如果默认构造函数或复制构造函数进行了引发,则不得使用 virtual 关键字声明析构函数,并且该析构函数必须能够用初始化为零的内存正常工作。
[Top]
concurrent_queue 类
与 std::queue 类一样,concurrency::concurrent_queue 类也允许您访问其 front 和 back 元素。 concurrent_queue 类可实现并发安全排队和取消排队操作。 concurrent_queue 类还提供了不是并发安全的迭代器支持。
concurrent_queue 和 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 方法返回之前,并发操作可能会导致队列增长或缩短。
下表显示了常见的不是并发安全的方法和运算符。
迭代器支持
concurrent_queue 提供了不是并发安全的迭代器。 我们建议您只为调试而使用这些迭代器。
concurrent_queue 迭代器只向前遍历元素。 下表显示了每个迭代器支持的运算符。
运算符 |
说明 |
---|---|
前进到队列中的下一项。 重载此运算符以提供前递增和后递增语义。 |
|
检索对当前项的引用。 |
|
检索指向当前项的指针。 |
[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 和化简并行执行映射操作的示例,请参见 如何:并行执行映射和减少操作。
在 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 |
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 返回迭代器而不是 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返回迭代器而不是 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 类通过以无锁方式提供对共享资源的访问,可以帮助您消除共享的状态。 因此,此类为使用同步机制(例如互斥体)同步多线程中共享数据的访问提供了一种替代方法。
方法和功能
下表显示了 combinable 类的某些重要方法。 有关所有 combinable 类方法的更多信息,请参见combinable 类。
方法 |
说明 |
---|---|
检索对与当前线程上下文相关联的局部变量的引用。 |
|
从 combinable 对象中移除所有线程本地变量。 |
|
使用提供的 combine 函数从所有线程本地计算的集合中生成最终值。 |
combinable 类是根据最终合并结果进行参数化的模板类。 如果调用默认构造函数,则 _Ty 模板参数类型必须具有默认构造函数和复制构造函数。 如果 _Ty 模板参数类型没有默认构造函数,则调用以初始化函数作为其参数的构造函数的重载版本。
在调用 combine 或 combine_each 方法之后,您可以在 combinable 对象中存储附加数据。 您还可以多次调用 combine 和 combine_each 方法。 如果 combinable 对象中的本地值没有发生更改,则每次调用 combine 和 combine_each 方法时,它们都会产生相同的结果。
示例
有关如何使用 combinable 类的示例,请参见以下主题:
[Top]
相关主题
如何:使用并行容器提高效率
演示如何使用并行容器以并行方式有效地存储和访问数据。如何:使用 combinable 提高性能
演示如何使用 combinable 类消除共享的状态,从而提高性能。如何:使用 combinable 组合多个集
演示如何使用 combine 函数合并数据的线程本地集。并行模式库 (PPL)
描述提供了命令性编程模型的 PPL,该模型提升了可伸缩性和易用性,以便开发并发应用程序。
引用
concurrent_unordered_multimap 类