在读写锁的读锁区域操作std::atomic是否线程安全?
结论先行
你这段代码并不安全,哪怕测试暂时正常,后续高并发或环境变化时大概率会触发崩溃或数据竞争,必须修正。
问题出在哪?
operator[]的隐藏风险:
哪怕你百分百确定someKey存在,std::map::operator[]的实现逻辑依然会先执行查找,若键不存在则插入默认元素。关键是,operator[]是非const成员函数,它的调用本身就违反了读锁的语义——读锁保护的是容器的“只读访问”,而非const函数意味着可能修改容器结构,C++标准没有保证这类操作在共享读锁下的安全性。哪怕实际没插入元素,内部的查找逻辑涉及容器内部红黑树的节点访问,和其他写线程的插入/删除操作并发时,会导致结构竞争,触发未定义行为。原子成员不代表容器访问安全:
你修改的tuple里的atomic<int>本身是线程安全的,但获取这个原子成员的过程(通过mp["someKey"])依赖于对std::map的非const访问,这一步在共享读锁下是不安全的,和原子操作本身的安全性无关。
正确的写法
用find替代operator[],find是const成员函数,能在共享读锁下安全调用:
void read() { std::shared_lock<std::shared_mutex> read_lock{mtx}; auto it = mp.find("someKey"); if (it != mp.end()) { std::get<2>(it->second).store(2); } }
find只会做纯查找,不会修改容器结构,在共享读锁下完全合规;同时原子成员的store操作本身是线程安全的,多个线程同时修改这个原子变量也不会有问题。
为什么测试没崩?
测试场景可能没触发极端竞争——比如没有其他线程同时对容器做插入/删除,或者线程调度刚好避开了operator[]内部结构访问和写操作的冲突。但未定义行为的特点就是“时好时坏”,换个编译器、CPU架构,或者高并发量上来,崩溃或数据错乱一定会出现。
内容的提问来源于stack exchange,提问作者Yves

