C++知识
文章目录
- 1.C++map为什么线程不安全?
- 2.map大量插入会有性能问题,为什么
- 3.set的应用场景
- 4.map set mutiset mutimap unordered_map unordered_set的底层实现、使用场景、优缺点
1.C++map为什么线程不安全?
其实STL中的容器都是线程不安全的,如果想要线程安全的话,就得自己加锁。
C++ 标准库从 C++11 开始明确规定:多个线程并发地对同一个 std::map 对象进行读写操作,是未定义行为(UB)——除非你自己加锁(如 std::mutex)。
为什么 std::map 不自带锁?
- 性能优先
标准库容器的设计哲学是零开销抽象(zero-overhead abstraction)。
如果每个操作都加锁,即使单线程程序也会被迫承担锁的开销,这违背了 C++ 的设计原则。 - 灵活性更高
用户可以根据具体场景选择锁的粒度(全局锁、分段锁、读写锁等)。
标准库提供了 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::multiset 或 unordered_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) |