特殊条件下std::map<int, std::atomic<T>>是否线程安全?
问题
通常情况下,多线程并发访问同一个std::map实例是线程不安全的。请问在同时满足以下两个特殊条件时,对该std::map的并发访问是否可以保证线程安全:
std::map实例完成初始化流程后,不再执行任何元素插入、删除操作,map的内部结构保持固定不变;std::map存储的value类型为std::atomic<T>。
回答
满足上述两个条件时,对该std::map的并发访问是线程安全的,依据如下:
- 从C++标准库的容器线程安全规则来看:只要不存在修改容器本身内部结构的操作(包括插入、删除元素、触发内部重排/重平衡的操作等),多线程并发对容器执行只读访问(包括
find查找、遍历已有节点、读取已有元素的key等)不存在数据竞争,属于标准定义的安全行为。第一个条件已经完全排除了容器结构修改的可能,所有调用的find方法都只是遍历map固定的红黑树结构、定位目标节点,全程不会修改容器的任何内部状态,不会触发容器层面的并发冲突。 - 对节点value的并发访问安全性由
std::atomic保证:std::atomic<T>类型的读写操作本身是原子的,默认内存顺序即可避免多线程下读写value产生的数据竞争,不会出现未定义行为。第二个条件刚好覆盖了value层面的并发访问场景,不存在对非原子变量的并发写/读写冲突。
使用-fsanitize=thread选项编译对应测试代码时,ThreadSanitizer(业内通用的线程数据竞争检测工具)未检测到任何线程安全问题,也验证了该场景的安全性。
可运行测试代码
#include<atomic> #include<thread> #include<map> #include<vector> #include<iostream> #include<chrono> class Demo{ public: Demo() { mp_.insert(std::make_pair(1, true)); mp_.insert(std::make_pair(2, true)); mp_.insert(std::make_pair(3, true)); } int Get(const int& integer, bool& flag) { const auto itr = mp_.find(integer); if( itr == mp_.end()) { return -1; } else { flag = itr->second; return 0; } } int Set(const int& integer, const bool& flag) { const auto itr = mp_.find(integer); if( itr == mp_.end()) { return -1; } else { itr->second = flag; return 0; } } private: std::map<int, std::atomic<bool>> mp_; }; int main() { Demo demo; std::vector<std::thread> vec; vec.push_back(std::thread([&demo](){ while(true) { for(int i=0; i<9; i++) { bool cur_flag = false; if(demo.Get(i, cur_flag) == 0) { demo.Set(i, !cur_flag); } std::this_thread::sleep_for(std::chrono::milliseconds(1000)); } } })); vec.push_back(std::thread([&demo](){ while(true) { for(int i=0; i<9; i++) { bool cur_flag = false; if(demo.Get(i, cur_flag)==0) { std::cout << "(" << i << "," << cur_flag <<")" << std::endl; } std::this_thread::sleep_for(std::chrono::milliseconds(10)); } } }) ); for(auto& thread:vec) { thread.join(); } }
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

