一、mutex 到底是什么?锁还是信号量?
mutex 是一把"互斥锁"(不是信号量,虽然有点像)。
- 名字来源:mut ual ex clusion(互斥)
- 作用:同一时间只允许一个线程进入某段代码
- 它是一个对象 (
std::mutex是个类,你mtx_就是它的一个实例)
mutex vs 信号量(区别)
| mutex(互斥锁) | 信号量(semaphore) | |
|---|---|---|
| 允许几个线程 | 1 个(0 或 1) | N 个(0~N) |
| 所有权 | 谁锁的谁解锁 | 没这要求 |
| 比喻 | 一个厕所隔间(一次进 1 人) | 有 N 个隔间的厕所 |
mutex 可以看成"计数为 1 的信号量 ",但语义更严(有所有权)。你项目用 mutex(一次只让一个线程改哈希表)。
mutex 是怎么"互斥"的
mutex 对象内部有个状态("锁着"还是"没锁"):
lock():如果没锁 → 锁上,进去;如果已锁 → 等(挂起)unlock():解锁,唤醒一个等待的线程
cpp
线程A: lock() → 成功(现在锁着)→ 干活 → unlock()
线程B: lock() → 已锁 → 等... 等A unlock → 成功 → 干活
二、mutex 和 lock_guard 是什么关系?
mutex 是"锁本身",lock_guard 是"自动管理锁的助手"。
cpp
mutex = 门锁(实体)
lock_guard = 帮你"进门锁上、出门自动开锁"的管家
关系 :lock_guard 包装 一个 mutex,调用它的 lock/unlock。
cpp
std::lock_guard<std::mutex> lock(mtx_);
// ↑ 管家 ↑ 它管的那把锁
展开看(lock_guard 内部做的事):
cpp
// 构造时:帮你锁
mtx_.lock();
// ... 你的代码 ...
// 析构时:自动解锁
mtx_.unlock();
为什么需要管家? 因为手动 lock/unlock 容易忘解锁 (导致死锁)。管家保证一定解锁。
对比:
mutex是"工具 ",lock_guard是"用工具的省心方式"。
三、const 是什么,为什么 const 函数里不能锁
const 的含义
const 修饰成员函数 = "这个函数不修改对象的内容"(只读)。
cpp
size_t size() const { // 承诺:不改 KvHash 的内容
return size_; // 只读,OK
}
为什么 const 函数里不能锁
关键 :const 函数里,所有成员都变成 const(因为函数承诺不改它们)。
cpp
size_t size() const {
// 这里 mtx_ 是 const std::mutex
mtx_.lock();
// ↑ lock() 会"修改" mtx_ 的状态(锁上)
// 但 mtx_ 是 const → 不允许修改 → 报错
}
为什么锁算"修改" :lock() 会改变 mutex 的内部状态(从"没锁"到"锁着")。这是"修改成员"。而 const 函数承诺"不改任何成员" → 冲突。
解决:mutable
cpp
mutable std::mutex mtx_;
mutable = "这个成员即使在 const 函数里也允许被修改"。
为什么锁该用 mutable:
const函数承诺"不改逻辑内容"(不改数据)- 但加锁/解锁只是"物理状态"变化,不改变数据
- 所以锁用
mutable是标准做法
几乎所有"带锁的类",锁成员都写
mutable。
四、总结图
cpp
mutex = 一把锁(互斥,一次进1个线程)
↓ 被包装
lock_guard = 自动上锁/解锁的管家(构造上锁,析构解锁)
↓ 用在
const 函数 = 承诺不改内容,但锁要 mutable 才能用
一句话
- mutex = 一把互斥锁(不是信号量;一次只让 1 个线程进)
- lock_guard = 包装 mutex 的管家,构造上锁、析构解锁(防忘记)
- const 函数 承诺不改成员,锁会改 mutex 状态 → 冲突 → 加
mutable让锁在 const 函数里也能用