std::map 上可能的线程不安全操作
Posted
技术标签:
【中文标题】std::map 上可能的线程不安全操作【英文标题】:Possible thread unsafe operation on std::map 【发布时间】:2015-12-15 13:14:45 【问题描述】:STL 不保证其集合的线程安全。但我想知道以下代码是否真的有效。
一个线程通过调用 [] 运算符在映射 m 上调用非常量操作,但使用映射上已经存在的键。据我所知,这只是调用 find() 并返回对 gcc 中迭代器的引用。另一个线程同时持有 m 的 const 迭代器。
问题是:断言会失败吗?
void doBracket(std::map<int, int>& m)
const auto& val = m[0];
std::cerr << val << std::endl;
void doIter(const std::map<int, int>& m)
auto zeroIter = m.find(0);
auto oneIter = m.find(1);
auto twoIter = m.find(2);
assert(zeroIter->second == 0);
assert(oneIter->second == 1);
assert(twoIter->second == 2);
int main()
std::map<int, int> m = 0, 0, 1, 1, 2, 2;
std::thread mutateThread doBracket, std::ref(m);
std::thread constThread doIter, std:ref(m);
mutateThread.join();
constThread.join();
这是 stl_map 的作用:
operator[](const key_type& __k)
// concept requirements
__glibcxx_function_requires(_DefaultConstructibleConcept<mapped_type>)
iterator __i = lower_bound(__k);
// __i->first is greater than or equivalent to __k.
if (__i == end() || key_comp()(__k, (*__i).first))
//handle this case
return (*__i).second;
【问题讨论】:
无论如何,您的代码都不是线程安全的。每个 ìtererator 更改操作都必须由互斥锁保护。 Dieter:我知道标准是这么说的。我对实际的实现更感兴趣。我更新了 stl_map 代码,这似乎暗示该代码实际上是线程安全的。 【参考方案1】:如果你查看http://www.cplusplus.com/reference/map/map/operator%5B%5D/,它会说关于 map::operator[]:
迭代器有效性
没有变化。
数据竞赛
容器被访问,并且可能被修改。 该函数访问一个元素并返回一个可用于修改其映射值的引用。同时访问其他元素是安全的。 如果该函数插入一个新元素,那么在容器中同时迭代范围是不安全的。
根据这一点,唯一可能不安全的情况是“如果函数插入新元素,则同时迭代容器中的范围”。由于您没有进行任何插入,因此结论一定是您的代码是安全的。
【讨论】:
【参考方案2】:您似乎已经确定了一个可以通过了解实现来超越标准的地方。但是,这种方法与使用find()
而不是operator[]()
的保证安全方法相比没有优势。
侧边栏:我个人觉得std::map::operator[]()
几乎没用。我总是想使用find()
、insert()
或其他东西。
【讨论】:
谢谢,约翰。实际上,我正在调试一种情况,我们恰好遇到这种情况(有两个线程,一个在映射上迭代,一个在已知键上执行括号运算符)。该地图似乎已损坏,并且认为可能是非常量括号运算符导致损坏。从实现来看,情况似乎并非如此。我同意应该将代码更改为使用 find()。但我想排除这种可能性。 嗯,你应该把代码改成使用find()
,看看能不能解决问题。如果您使用的是 Linux,您还应该考虑使用 valgrind 和 helgrind 等工具来加快故障排除速度。以上是关于std::map 上可能的线程不安全操作的主要内容,如果未能解决你的问题,请参考以下文章