std::lock与std::unique_lock配合的工作机制及使用优势咨询
问题背景与疑问
我正在编写涉及银行账户转账的多线程代码,目标是安全完成账户间转账、避免竞态条件,目前用std::mutex保护账户余额。我没有直接把std::mutex传入std::lock,而是用std::unique_lock包裹后再传入,有以下疑问:
- std::lock如何与std::unique_lock对象协作工作?
- 是否由std::lock负责实际锁定from和to对应的mutex,而std::unique_lock仅负责锁的生命周期管理(如作用域结束时自动释放)?
- std::lock是否会调用std::unique_lock的lock()方法?
- 与直接将std::mutex对象传入std::lock相比,使用std::unique_lock配合的优势是什么?
相关代码
struct bank_account { bank_account(int balance) : mtx(), balance{ balance } {} std::mutex mtx; int balance; }; void transfer(bank_account& from, bank_account& to, int amount) { std::unique_lock<std::mutex> from_Lock(from.mtx, std::defer_lock); std::unique_lock<std::mutex> to_Lock(to.mtx, std::defer_lock); std::lock(from_Lock, to_Lock); if (amount <= from.balance) { std::cout << "Before: " << amount << " from: " << from.balance << " to: " << to.balance << '\n'; from.balance -= amount; to.balance += amount; std::cout << "After: " << amount << " from: " << from.balance << " to: " << to.balance << '\n'; } else { std::cout << amount << " is greater than " << from.balance << '\n'; } } int main() { bank_account A(200); bank_account B(100); std::vector<std::jthread> workers; workers.reserve(20); for (int i = 0; i < 10; ++i) { workers.emplace_back(transfer, std::ref(A), std::ref(B), 20); workers.emplace_back(transfer, std::ref(B), std::ref(A), 10); } }
问题解答
1. std::lock如何与std::unique_lock对象协作工作?
std::lock是通用的多锁操作函数,接受符合Lockable概念的对象作为参数,std::unique_lock正好满足这个要求。当你传入用std::defer_lock构造的unique_lock(初始未锁定状态)时,std::lock会通过一种避免死锁的算法(比如按mutex的内存地址顺序锁定),依次操作每个unique_lock关联的底层std::mutex完成锁定。锁定成功后,unique_lock会更新自身状态为"持有锁",后续的锁状态维护都由它负责。
2. 是否由std::lock负责实际锁定from和to对应的mutex,而std::unique_lock仅负责锁的生命周期管理?
完全正确。std::lock的核心作用是安全地完成多mutex的锁定,避免死锁;而std::unique_lock的核心职责是管理锁的生命周期:当unique_lock离开作用域(比如函数返回、异常抛出)时,会自动调用unlock()释放关联的mutex,彻底避免手动解锁遗漏导致的死锁。此外,unique_lock还支持手动解锁/重新锁定,提供比直接操作mutex更灵活的锁控制。
3. std::lock是否会调用std::unique_lock的lock()方法?
是的。std::lock对Lockable类型的要求就是必须提供lock()、unlock()等成员函数。std::unique_lock::lock()内部会调用其关联的std::mutex::lock(),所以std::lock最终还是通过unique_lock的接口完成了底层mutex的锁定操作。
4. 与直接将std::mutex对象传入std::lock相比,使用std::unique_lock配合的优势是什么?
直接传递std::mutex给std::lock的话,你必须手动调用unlock()释放锁,这在复杂逻辑(比如分支、异常)中极易出错。而std::unique_lock配合的优势主要有:
- 自动释放锁:无论正常执行还是异常抛出,
unique_lock都会在作用域结束时自动解锁,彻底消除遗忘解锁的风险。 - 灵活的锁控制:可以手动调用
unlock()提前释放锁(比如转账完成后无需持有锁时),也能后续重新调用lock()获取锁,优化锁的持有时间。 - 支持延迟锁定:通过
std::defer_lock构造的unique_lock处于未锁定状态,正好适配std::lock的多锁死锁避免逻辑——如果直接用std::mutex,你无法创建一个未锁定的包装来交给std::lock处理。 - 可移动性:
std::unique_lock是可移动的,允许你将锁的所有权转移到其他函数或对象中,而std::mutex本身不可移动、不可复制,灵活性远不如前者。
内容的提问来源于stack exchange,提问作者sam

