Intel TBB 提供高并发的容器类,Windows或者Linux线程能使用这些容器类或者和基于task编程相结合(TBB)。
一个并发容器允许多线程同时对容器访问和更改条例,典型的C++STL容器类不允许 并发更新,尝试并行更改他们引起恶化容器。STL容器能使用互斥锁来包装,目的让他们安全访问,通过只让一个线程同时操作一个容器,但是这种方式消除了并行,限制了并行提速。
Intel TBB提供的容器有更高水准的并发性,通过以下方法:
注意高并发容器是有代价的,他们典型的有更高的消耗比STL容器,对高并发容器操作比STL容器消耗更多,因此,当使用高并发容器比STL容器提速的时候,他们是优于串行的性能。
concurrenthashmap<Key,T,HashCompare> 是一个hash表,允许并行访问,表是一个从Key到类型T的映射,类型HashCompare定义怎样hash一个Key和怎样比较2个Key。
下面的例子建立一个concurrent_hash_map,这个Key是字符串,对应的数据是矩阵Data中字符串出现的次数。
#include "tbb/concurrent_hash_map.h"
#include "tbb/blocked_range.h"
#include "tbb/parallel_for.h"
#include
using namespace tbb;
using namespace std;
// Structure that defines hashing and comparison operations for user's
type.
struct MyHashCompare {
static size_t hash( const string& x ) {
size_t h = 0;
for( const char* s = x.c_str(); *s; ++s )
h = (h*17)^*s;
return h;
}
//! True if strings are equal
static bool equal( const string& x, const string& y ) {
return x==y;
}
};
// A concurrent hash table that maps strings to ints.
typedef concurrent_hash_map<string,int,MyHashCompare> StringTable;
// Function object for counting occurrences of strings.
struct Tally {
StringTable& table;
Tally( StringTable& table_ ) : table(table_) {}
void operator()( const blocked_range<string*> range ) const {
for( string* p=range.begin(); p!=range.end(); ++p ) {
StringTable::accessor a;
table.insert( a, *p );
a->second += 1;
}
}
};
const size_t N = 1000000;
string Data[N];
void CountOccurrences() {
// Construct empty table.
StringTable table;
// Put occurrences into the table
parallel_for( blocked_range<string*>( Data, Data+N, 1000 ),
Tally(table) );
// Display the occurrences
for( StringTable::iterator i=table.begin(); i!=table.end(); ++i )
printf("%s %d\n",i->first.c_str(),i->second);
}
concurrenthashmap 扮演一个类型 std::pair<constKey,T> 元素的容器,当访问一个容器元素时,你对更新它或者读取它感兴趣,模板类concurrent_hash_map支持这2个目的,对应类accessor和const_accessor,其担当智能指针,accessor表示更新(write)访问,一旦它指向一个元素,所有其他尝试寻找这个表的key都会被阻止,直到accessor被完成。const_accessors是类似的,除了只表示读访问,多const_accessors能同时指向相同的元素,在频繁的读但是很少更新的情况下,这个特点极大的改善并行情形。
方法find和insert采用accessor或者const_accessor作为一个参数,告诉concurrent_hash_map是否你要求更新或者只读访问,一旦这个方法返回,直到访问结束,accessor或者const_accessor才会被销毁,因为对元素的访问阻止其他线程,所以尽可能的缩减accessor或者const_accessor生命周期,为了这么做,我们在内部块中声明它,尽可能在块结束前释放这个accessor,下面的例子是重写循环体,使用release结束accessor的生命周期:
StringTable accessor a;
for( string* p=range.begin(); p!=range.end(); ++p ) {
table.insert( a, *p );
a->second += 1;
a.release();
}
方法remove(key)也能同时操作,它隐式请求写访问,因此,在删除这个Key之前,它等待其他现存key上的访问结束。