C++知识

文章目录

1.C++map为什么线程不安全?

其实STL中的容器都是线程不安全的,如果想要线程安全的话,就得自己加锁。

C++ 标准库从 C++11 开始明确规定:多个线程并发地对同一个 std::map 对象进行读写操作,是未定义行为(UB)------除非你自己加锁(如 std::mutex)。

为什么 std::map 不自带锁?

  1. 性能优先
    标准库容器的设计哲学是零开销抽象(zero-overhead abstraction)。
    如果每个操作都加锁,即使单线程程序也会被迫承担锁的开销,这违背了 C++ 的设计原则。
  2. 灵活性更高
    用户可以根据具体场景选择锁的粒度(全局锁、分段锁、读写锁等)。
    标准库提供了 std::mutex、std::shared_mutex 等工具,让用户自己组合。

2.map大量插入会有性能问题,为什么

std::map 在"大量插入"场景下出现性能瓶颈,并不是因为它"不会用 CPU",而是由它的底层数据结构(红黑树)和接口语义共同决定的。下面把原因拆成 4 个层面,一层层展开:

① 数据结构层面:红黑树的 O(log n) 代价

插入 = 搜索插入点 + 树重平衡。

每 insert/operator[] 都要从根节点一路比较到叶子,找到插入位置;随后可能触发 旋转 + 颜色翻转,这些都是 O(log n) 的 CPU 计算。

当 n 达到百万、千万级时,log₂(n) 依旧不算小,而且每一次操作都独立走一遍完整路径,CPU 分支预测失败率高、cache miss 高。

② 内存分配层面:节点级分配带来的负担

红黑树是 "节点式容器"(node-based container),每插入一个元素就至少一次 new/malloc。

大量小对象分配 →内存碎片;分配器锁竞争(默认 glibc ptmalloc 的 global arena 锁);cache locality 极差:节点散落在堆各处,遍历时随机访问,TLB & cache 命中率低。

③ 接口语义层面:每次插入都要"唯一键"检查

std::map::insert 和 operator[] 都要 先查重,保证键唯一。就算你事先知道键不会重复,也绕不开这次查找;而哈希表(unordered_map)可以通过 reserve + emplace_hint 避免重复检查。

④ 并发层面:自带无锁机制 → 线程竞争放大问题

如果你在多线程环境用全局 mutex 保护 std::map,锁粒度是整个对象;大量插入让这把锁成为 热点,线程上下文切换/睡眠开销迅速放大(之前谈过的互斥锁 vs 自旋锁问题)。

综合症状 = 计算 + 内存 + 并发 三重放大

维度 开销来源 量级
CPU 红黑树 log n 比较 + 旋转 10⁷ 次插入时 log₂(10⁷) ≈ 23
内存 每元素一次 new + 指针开销 额外 2~3 倍内存放大
并发 全局 mutex 或分配器锁 线程越多越慢

std::map 大量插入慢,是因为 "红黑树的节点级操作 + 每次 log n 计算 + 频繁内存分配" 三者叠加;把容器换成哈希表、把一次性插入变成批量构造,或者把 map 拆掉分片,都能让性能上一个数量级。

3.set的应用场景

需求 容器
需要去重、有序遍历、范围查询是否存在 std::set / std::multiset
只去重/查询、不关心顺序 std::unordered_set(平均 O(1))
键可重复 std::multisetunordered_multiset
极高并发读写 第三方并发 set(tbb::concurrent_hash_set, folly::F14)

4.map set mutiset mutimap unordered_map unordered_set的底层实现、使用场景、优缺点

容器 底层实现 典型场景 优点 缺点
map 红黑树 键值对有序、范围查询、遍历 有序、可范围遍历、最坏O(log n) 内存碎片大、插入慢、cache差
set 红黑树 去重+有序遍历、排行榜 有序、唯一键、支持lower_bound 同map缺点
multiset 红黑树 允许重复键的有序集合 保留排序、可存重复 同map缺点
multimap 红黑树 一键多值的有序存储 一键多值、范围遍历 同map缺点
unordered_map 哈希表 键值对无序、高速增删查 平均O(1)、内存连续性好 无序、rehash开销、最坏O(n)
unordered_set 哈希表 高速去重、存在性检查 平均O(1)、简单高效 无序、rehash开销、最坏O(n)
相关推荐
让我们一起加油好吗几秒前
【数论】欧拉定理 && 扩展欧拉定理
c++·算法·数论·1024程序员节·欧拉定理·欧拉降幂·扩展欧拉定理
Yupureki7 分钟前
从零开始的C++学习生活 14:map/set的使用和封装
c语言·数据结构·c++·学习·visual studio·1024程序员节
一匹电信狗14 分钟前
【LeetCode_876_2.02】快慢指针在链表中的简单应用
c语言·数据结构·c++·算法·leetcode·链表·stl
keineahnung234514 分钟前
C++中的Aggregate initialization
c++·1024程序员节
胖咕噜的稞达鸭17 分钟前
算法入门---专题二:滑动窗口2(最大连续1的个数,无重复字符的最长子串 )
c语言·数据结构·c++·算法·推荐算法·1024程序员节
Yupureki23 分钟前
从零开始的C++学习生活 15:哈希表的使用和封装unordered_map/set
c语言·数据结构·c++·学习·visual studio·1024程序员节
Q一件事27 分钟前
R语言随机森林分析显示R方和P值
开发语言·随机森林·r语言
我是华为OD~HR~栗栗呀27 分钟前
华为OD-Java面经-21届考研
java·c++·后端·python·华为od·华为·面试
Mr.Jessy31 分钟前
JavaScript学习第六天:函数
开发语言·前端·javascript·学习·html·1024程序员节
考虑考虑36 分钟前
流收集器
java·后端·java ee