😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《C++11》系列专栏,相信一份耕耘一份收获,我会分享相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!
多线程同步机制中 lock_guard 与 unique_lock 的使用区别
引言
在 C++ 多线程编程中,同步机制是确保数据一致性和线程安全的关键。C++11 标准引入了两种新的锁定机制:std::lock_guard
和 std::unique_lock
。这两种锁定器都用于自动管理互斥锁的生命周期,但它们在设计、使用方式和适用场景上存在一些重要的区别。本文将深入分析这两种锁定器的区别,并探讨它们在多线程同步机制中的使用。
1. std::lock_guard
的特性
std::lock_guard
是一个简单的 RAII(Resource Acquisition Is Initialization)锁定器,它在构造时自动获取给定的互斥锁,并在析构时自动释放。它的使用非常简单,但功能也相对有限。
主要特性:
- 自动锁定和解锁。
- 不能手动解锁。
- 不可移动,即不能被复制或移动。
- 适用于简单的锁管理需求。
2. std::unique_lock
的特性
std::unique_lock
是一个更为灵活的锁定器,它提供了对互斥锁的独占访问,并可以手动锁定和解锁。
主要特性:
- 可以手动锁定和解锁。
- 可移动,不可复制。
- 可以用于条件变量的等待操作。
- 适用于需要更复杂锁管理的场景。
3. 使用场景比较
简单锁管理
对于简单的锁管理,std::lock_guard
是一个非常好的选择。它足够轻量级,且使用起来非常简单。
使用 std::lock_guard
示例:
void simple_function(std::mutex& m) {
std::lock_guard<std::mutex> lk(m);
// 临界区代码
}
复杂锁管理
对于需要手动控制锁定时机或与条件变量配合使用的场景,std::unique_lock
是更好的选择。
使用 std::unique_lock
示例:
void complex_function(std::mutex& m, std::condition_variable& cv, bool& ready) {
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [&]{ return ready; });
// 临界区代码,ready 为 true 后执行
}
4. 锁的管理方式比较深入分析
锁定时机的深入分析
std::lock_guard 的锁定时机是在其构造函数执行时。这意味着一旦 std::lock_guard
对象被创建,它马上会尝试获取提供的互斥锁。这种立即锁定的行为使得 std::lock_guard
的使用非常简单,但缺乏灵活性。
示例代码:
{
std::lock_guard<std::mutex> guard(mutex);
// 临界区:guard 对象创建后立即进入临界区
}
// 非临界区:guard 对象析构时自动离开临界区
std::unique_lock 提供了更多的灵活性,允许开发者选择锁定的时机。如果构造函数调用时不提供互斥锁,或者使用 std::defer_lock
作为构造参数,那么 std::unique_lock
不会立即锁定互斥锁,而是允许开发者在之后手动调用 lock()
方法。
示例代码:
std::unique_lock<std::mutex> unique_lock(mutex, std::defer_lock);
// 执行其他操作...
unique_lock.lock(); // 显式锁定互斥锁
{
// 临界区:unique_lock.lock() 调用后进入临界区
}
unique_lock.unlock(); // 显式解锁互斥锁
// 非临界区:unique_lock 对象析构时自动离开临界区
手动解锁的深入分析
std::lock_guard 不支持手动解锁,这是它的设计哲学的一部分,旨在提供异常安全的简单锁管理。这意味着开发者不需要(也不能)在代码中显式地调用解锁方法,减少了因忘记解锁而造成死锁的风险。
相比之下,std::unique_lock 支持手动解锁,这在某些复杂的同步场景中非常有用。例如,当一个线程需要在等待某个条件变量之前解锁互斥锁,然后在被通知后重新锁定互斥锁时。
示例代码:
unique_lock.lock();
// 等待条件变量
unique_lock.unlock();
condition_variable.wait(lock, []{ return condition_met; });
unique_lock.lock(); // 重新锁定互斥锁
作用域结束时的解锁的深入分析
无论是 std::lock_guard
还是 std::unique_lock
,它们都会在其作用域结束时自动解锁互斥锁。这是通过对象的析构函数来实现的。对于 std::lock_guard
,这是其唯一的解锁机制;而对于 std::unique_lock
,这是最后的保障,即使没有手动调用 unlock()
。
5. 性能考虑深入分析
在大多数情况下,std::lock_guard
和 std::unique_lock
的性能差异不大,因为它们的锁管理成本主要在于互斥锁本身的操作。然而,在一些特定场景下,std::unique_lock
的灵活性可以带来性能优势:
- 减少锁的持有时间:使用
std::unique_lock
可以只在必要时持有锁,减少锁的持有时间,从而减少线程争用。 - 避免不必要的锁定:在某些情况下,如果条件不满足,
std::unique_lock
可以避免锁定互斥锁,从而减少无用的锁竞争。 - 与条件变量的配合:
std::unique_lock
可以与条件变量一起使用,实现更高效的线程同步,避免忙等待。
示例代码:
std::unique_lock<std::mutex> lock(mutex);
condition_variable.wait(lock, []{ return data_ready; });
// 当 data_ready 为 true 时,锁自动被持有,无需额外的锁定操作
在这个示例中,std::unique_lock
与条件变量一起使用,实现了在条件不满足时的等待,减少了不必要的 CPU 消耗。
结语
std::lock_guard
和 std::unique_lock
各有其适用场景和优势。std::lock_guard
的简单性和自动性使其成为快速简单锁管理的首选。而 std::unique_lock
的灵活性和手动控制能力使其更适合复杂的同步需求。
祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~
🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经。
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~