迭代器失效规则是否意味着线程安全?
在这个Stack Overflow答案中,它列出了C ++ 11中标准容器的迭代器失效规则。 特别是有插入 :
[multi]{set,map}
:所有迭代器和引用不受影响[23.2.4 / 9] unordered_[multi]{set,map}
:所有迭代器在发生重新哈希时都失效,但引用不受影响[23.2.5 / 8]。 如果插入不会导致容器的大小超过z * B
,则不会发生重新散列,其中z
是最大加载因子, B
是当前桶的数量。 [23.2.5 / 14]
擦除 :
[multi]{set,map}
和unordered_[multi]{set,map}
:只有迭代器和对擦除元素的引用才会失效
这些规则是否意味着我可以安全地在一个线程中插入和擦除,并安全地在另一个线程访问中查找(使用find
)元素,只要这些元素不是在第一个线程中插入和擦除的元素,并确保重新哈哈哈没有发生?
如果不是,这些规则究竟意味着什么?
在C ++ std
容器上有两种操作。 Reader和Writer操作(这些不是标准使用的术语,但是这更容易)。 另外,对容器中的元素进行操作。
甲const
方法是一个阅读器的方法,因为是“查找”功能,这些功能只非const
,因为它们返回非const
参考的元件(或类似的)。 标准中有一个完整的列表,但常识应该起作用。 vector::operator[]
, begin()
, map::find()
都是“读者”。 map::operator[]
不是 “Reader”。
您可以有任何数量的线程在同一时间参与阅读器操作没有问题。
如果您有任何线程参与Writer操作,则容器上不会出现其他访问 。
您无法同时安全地安装一台Reader和一台Writer。 如果你有任何作家,你必须排除所有其他访问。
您可以一次安全地拥有1337个阅读器。
对元素的操作有些类似,除了写入元素只需要排除对该元素的其他访问权限。 你有责任让你的const
方法相互发挥很好。 ( std
保证容器的const
方法只会调用元素的const
方法)
请注意,更改std::
associative容器中的排序顺序是UB,而无需修改容器。
一个没有失效的迭代器,你只需要对元素进行操作,我相信它会作为元素的操作。 只需要在该元素上进行同步。
请注意, std::vector<bool>
不遵循上述规则。
如果违反上述规则,C ++ std
库不会阻止你。 它只是说明存在竞争条件 - 也就是未定义的行为。 任何事情都可能发生。 在C ++ std
库中,如果你不需要什么东西,你不需要支付它:而单线程使用这些容器不需要同步的重量。 所以你不会默认它。
std::shared_timed_mutex
或者std::experimental::shared_mutex
都可以用来保证上面的结果。 用于写入的unique_lock
和用于读取的shared_lock
。 写访问的元素必须是shared_lock
把守的容器上编辑,并以某种方式防护,以防无死锁重叠访问相同的元件。
迭代器失效规则与线程安全规则相对正交。
事实上,容器元素的迭代器不会失效,绝不意味着容器本身的线程安全。 例如,size成员变量需要自动修改,这与迭代器在插入/删除时被无效(或不)的问题完全不同。
TL;博士; 没有。
这些规则只是简单地告诉你一个元素的迭代器是否被操作无效。 例如,当一个矢量调整大小时,底层数组在其他地方被重新分配,所以如果你有一个元素的迭代器(或指针),它在调整大小后将不再有效(因为它会指向旧数组的已删除元素)。
使用find
意味着遍历,至少在元素的一个子集上。 在[multi]{set,map}
上insert
和erase
导致重新平衡底层树,这会影响节点之间的链接。 如果重新平衡发生在同一时间find
,坏事会发生。
同样,如果您在unordered_[multi]{set,map}
insert
或erase
期间尝试find
,则会发生不好的事情。 insert
可能导致重新哈希。 insert
和erase
需要链接/取消链接列表中的元素。 如果某个find
在链接/取消链接期间在列表中搜索,则会丢失。
[]
on [unordered][multi]{set,map}
是“找到并插入,如果找不到”的简写。 at
是find
简写。 所以不,这些也不安全。
如果您有一个现有的迭代器到[multi]{set,map}
,则可以继续取消引用(但不递增/递减)该迭代器,而插入或删除另一个元素。 对于unordered_[multi]{set,map}
,只有在可以保证在插入操作下不会发生重新刷新的情况下(它在擦除过程中从不发生),这才是真实的。