多线程同步机制中 lock_guard 与 unique_lock 的使用区别

在这里插入图片描述

😎 作者介绍:欢迎来到我的主页👈,我是程序员行者孙,一个热爱分享技术的制能工人计算机本硕,人工制能研究生。公众号:AI Sun(领取大厂面经等资料),欢迎加我的微信交流:sssun902
🎈 本文专栏:本文收录于《C++11》系列专栏,相信一份耕耘一份收获,我会分享相关学习内容,不说废话,祝大家都offer拿到手软
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥随时欢迎您跟我沟通,一起交流,一起成长、进步!

多线程同步机制中 lock_guard 与 unique_lock 的使用区别

引言

在 C++ 多线程编程中,同步机制是确保数据一致性和线程安全的关键。C++11 标准引入了两种新的锁定机制:std::lock_guardstd::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_guardstd::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_guardstd::unique_lock 各有其适用场景和优势。std::lock_guard 的简单性和自动性使其成为快速简单锁管理的首选。而 std::unique_lock 的灵活性和手动控制能力使其更适合复杂的同步需求。

祝大家学习顺利~
如有任何错误,恳请批评指正~~
以上是我通过各种方式得出的经验和方法,欢迎大家评论区留言讨论呀,如果文章对你们产生了帮助,也欢迎点赞收藏,我会继续努力分享更多干货~


🎈关注我的公众号AI Sun可以获取Chatgpt最新发展报告以及腾讯字节等众多大厂面经
😎也欢迎大家和我交流,相互学习,提升技术,风里雨里,我在等你~


  • 36
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员行者孙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值