Visual C++ 2010 中的 STL 映射实现和线程安全

Posted

技术标签:

【中文标题】Visual C++ 2010 中的 STL 映射实现和线程安全【英文标题】:STL map implementation in Visual C++ 2010 and thread safety 【发布时间】:2013-07-11 19:25:38 【问题描述】:

我知道,只要不需要调整大小,就可以读取一些单元格并同时写入 STL 向量的不同单元格。我想知道如果我保证每个线程访问/插入不同的键,是否允许同时获取某些键的值并同时将新的键值对插入到 Visual C++ 2010 中的 STL 映射中。

来自 http://www.cplusplus.com/reference/map/map/operator[]/:

数据竞赛:

容器被访问,并且可能被修改。功能 访问元素并返回可用于修改的引用 它的映射值。同时访问其他元素是安全的。如果 该函数插入一个新元素,同时迭代范围 容器不安全。

也就是说,如果我插入一个新元素,我就不能遍历容器。问题是访问不同的元素是否需要遍历容器。那这样安全吗?

如果我保证容器的大小永远不会超过 N 是否安全?然后也许地图的内部数据结构可以预先分配并保持不变——就像向量的内部结构一样,只要向量没有调整大小。

我知道有可用的 map 线程安全实现,但它们可能要慢得多,所以我想知道标准 map 是否足以满足我的需要,因为我正在修改的代码是我的应用程序。

谢谢, 迈克尔

【问题讨论】:

容器保证在插入开始和插入结束时是一致的,但是在两者之间呢?那将是一个奇迹。 将地图保持在 N 以下大小将对线程安全性绝对没有影响。即使树木保持大致恒定的大小,它们也必须旋转。 查找现有元素需要迭代。 编辑是安全的。 【参考方案1】:

我想知道如果我保证每个线程访问/插入不同的键,是否允许同时获取某些键的值并同时将新的键值对插入 Visual C++ 2010 中的 STL 映射。

不,这是不允许的。查找涉及内部数据结构的遍历(对于 std::map,内部表示的常用方法是 binary search tree,如 Red–black tree)。另一方面,insert 修改了内部结构。

如果同时查找和插入是线程安全的,那么每次访问,即使在单线程环境中,也会涉及高昂的操作同步成本,这与 C++ 原则相矛盾 - “你为你不使用的东西付费” .

Thread Safety in MSVC 2010:

如果一个线程正在写入单个对象,则必须保护同一线程或其他线程上对该对象的所有读取和写入。例如,给定一个对象 A,如果线程 1 正在写入 A,则必须阻止线程 2 读取或写入 A。

也就是说,如果您在其他线程中的插入操作之前已经引用了元素 - 通过该引用访问元素是安全的,因为在内部重新平衡期间不会移动对象。

ISO C++11 23.2.4/9:

insert 和 emplace 成员不应影响迭代器的有效性和对容器的引用,erase 成员应仅使迭代器和对被擦除元素的引用无效。


MSVC 2012(但不是 MSVC 2010)有 concurrency::concurrent_unordered_map 关联容器(注意它是 无序的,这使得它类似于 std::unordered_map,即它需要键的哈希和相等而不是严格的弱订购):

concurrent_unordered_map 类是一个并发安全容器,它控制 std::pair<const _Key_type, _Element_type> 类型的可变长度元素序列。该序列以一种支持并发安全追加、元素访问、迭代器访问和迭代器遍历操作的方式表示。


Intel TBB 库有类似的容器 - tbb::concurrent_unordered_map:

支持并发插入和遍历的关联容器的模板类。

【讨论】:

以上是关于Visual C++ 2010 中的 STL 映射实现和线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Visual C++ 2010 在调试时拒绝显示 std::string 值。显示 <Bad Ptr>

在 Visual Studio 2012 中调试 C++ 代码时跳过 STL 代码?

C++ STL 问题:分配器

C++ 中的多个定义 (Visual Basic 2010)

Visual Studio C++ 2010 Express中的程序错误

访问 Matrix 类 (C++) 中的二维数组 (Visual Studio 2010)