C++中unique_lock和lock_guard区别

目录

1.自动锁定与解锁机制

2.灵活性

3.所有权转移

4.可与条件变量配合使用

5.性能开销


在 C++ 中,std::unique_lock 和 std::lock_guard 都属于标准库 <mutex> 中的互斥锁管理工具,用于简化互斥锁的使用并确保线程安全。但它们存在一些显著区别,下面为你详细介绍:

1.自动锁定与解锁机制

1) std::lock_guard:一个轻量级的互斥锁包装器,采用了 RAII(资源获取即初始化)技术。当 std::lock_guard 对象被创建时,它会自动锁定所关联的互斥锁;当对象离开其作用域时,会自动解锁该互斥锁。它的设计遵循最小化原则,仅提供最基本的锁管理功能,没有额外的开销。其核心实现原理可以简化为:

template<classMutex>
classlock_guard {
public:explicitlock_guard(Mutex& m) : mutex(m) {mutex.lock();}~lock_guard() {mutex.unlock();}lock_guard(const lock_guard&) = delete;lock_guard& operator=(const lock_guard&) = delete;private:Mutex& mutex;
};

        示例代码如下:

#include <iostream>
#include <mutex>
#include <thread>std::mutex mtx;void printMessage() {std::lock_guard<std::mutex> lock(mtx);std::cout << "This message is protected by lock_guard." << std::endl;// 当 lock_guard 对象离开作用域时,互斥锁会自动解锁
}int main() {std::thread t(printMessage);t.join();return 0;
}

2) std::unique_lock: 同样基于 RAII 技术,在对象销毁时会自动解锁互斥锁。不过,它的锁定和解锁操作更加灵活,可以在对象创建时选择不立即锁定互斥锁,也可以在对象生命周期内手动锁定和解锁。unique_lock 是 C++11 标准中引入的更高级的锁管理器,设计目标是提供更灵活的锁管理能力。其核心接口包括:

class unique_lock {
public:// 构造时可选立即加锁、延迟加锁或尝试加锁unique_lock(mutex_type& m, std::defer_lock_t) noexcept;unique_lock(mutex_type& m, std::try_to_lock_t);unique_lock(mutex_type& m, std::adopt_lock_t);// 转移构造函数unique_lock(unique_lock&& other) noexcept;// 手动控制接口voidlock();booltry_lock();voidunlock();// 状态查询explicitoperatorbool()constnoexcept;boolowns_lock()constnoexcept;
};

            示例代码如下:

    #include <iostream>
    #include <mutex>
    #include <thread>std::mutex mtx;void printMessage() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock);// 手动锁定互斥锁lock.lock();std::cout << "This message is protected by unique_lock." << std::endl;// 手动解锁互斥锁lock.unlock();
    }int main() {std::thread t(printMessage);t.join();return 0;
    }

    std::unique_lock 允许在对象生命周期内多次手动调用 lock()unlock() 和 try_lock() 方法。这在需要在临界区内进行部分操作后暂时释放锁,执行一些非关键操作,然后再次锁定的场景中很有用。如:

    #include <iostream>
    #include <mutex>
    #include <thread>std::mutex mtx;void work() {std::unique_lock<std::mutex> lock(mtx);std::cout << "Entered critical section." << std::endl;// 执行部分临界区操作lock.unlock();std::cout << "Temporarily released the lock." << std::endl;// 执行一些非关键操作lock.lock();std::cout << "Re - entered critical section." << std::endl;// 继续执行临界区操作
    }int main() {std::thread t(work);t.join();return 0;
    }

    2.灵活性

    1)std::lock_guard: 功能相对单一,缺乏灵活性。一旦创建,就会立即锁定互斥锁,并且在其生命周期内无法手动解锁,只能在对象离开作用域时自动解锁。

    2)std::unique_lock:具有更高的灵活性。它支持三种锁定策略:

    std::defer_lock_t   // 延迟锁定
    std::try_to_lock_t  // 尝试锁定
    std::adopt_lock_t   // 接管已锁定状态
    • 延迟锁定(std::defer_lock:创建 std::unique_lock 对象时,可以选择不立即锁定互斥锁。这在需要先进行一些准备工作,之后再锁定互斥锁的场景中非常有用。如:
    #include <iostream>
    #include <mutex>
    #include <thread>std::mutex mtx;void work() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock);// 进行一些无需加锁的准备工作std::cout << "Doing some preparation work..." << std::endl;lock.lock();std::cout << "Critical section entered." << std::endl;// 临界区代码lock.unlock();std::cout << "Critical section exited." << std::endl;
    }int main() {std::thread t(work);t.join();return 0;
    }
    • 尝试锁定(std::try_to_lock:使用 std::try_to_lock 可以尝试锁定互斥锁,如果互斥锁当前已被其他线程锁定,std::unique_lock 对象不会阻塞,而是立即返回,可通过 owns_lock() 方法判断是否成功锁定。如:
    #include <iostream>
    #include <mutex>
    #include <thread>std::mutex mtx;void work() {std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);if (lock.owns_lock()) {std::cout << "Successfully locked the mutex." << std::endl;// 临界区代码} else {std::cout << "Failed to lock the mutex, doing other work." << std::endl;// 执行其他不需要锁定互斥锁的工作}
    }int main() {std::thread t(work);t.join();return 0;
    }

    3.所有权转移

    • std::lock_guard
      • 不支持所有权转移,即不能将一个 std::lock_guard 对象的互斥锁所有权转移给另一个对象。
    • std::unique_lock
      • 支持所有权转移,可以通过移动构造函数或移动赋值运算符将互斥锁的所有权从一个 std::unique_lock 对象转移到另一个对象。这在函数返回 std::unique_lock 对象或需要在不同作用域之间传递锁的所有权时非常有用。
      • 示例代码如下:
    #include <iostream>
    #include <mutex>
    #include <thread>std::mutex mtx;std::unique_lock<std::mutex> getLock() {std::unique_lock<std::mutex> lock(mtx);return std::move(lock);
    }void printMessage() {std::unique_lock<std::mutex> lock = getLock();std::cout << "This message is protected by transferred unique_lock." << std::endl;
    }int main() {std::thread t(printMessage);t.join();return 0;
    }

    4.可与条件变量配合使用

    std::unique_lock 能够与 std::condition_variable 一起使用,std::condition_variable 的 wait()wait_for() 和 wait_until() 等方法要求传入 std::unique_lock 对象,因为在等待期间需要释放和重新获取锁。

    #include <iostream>
    #include <mutex>
    #include <condition_variable>
    #include <thread>std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;void worker() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return ready; });std::cout << "Worker thread is working." << std::endl;
    }int main() {std::thread t(worker);{std::lock_guard<std::mutex> lock(mtx);ready = true;}cv.notify_one();t.join();return 0;
    }

    5.性能开销

    • std::lock_guard
      • 由于其功能简单,没有额外的状态管理,因此性能开销相对较小,适合用于简单的锁定场景。
    • std::unique_lock
      • 为了支持更多的灵活性,std::unique_lock 需要维护更多的状态信息,因此性能开销相对较大。在对性能要求极高且锁定逻辑简单的场景下,使用 std::lock_guard 更为合适。

    综上所述,std::lock_guard 适用于简单的锁定场景,追求简洁性和较低的性能开销;而 std::unique_lock 则适用于需要更复杂锁定逻辑、支持所有权转移的场景,但会带来一定的性能开销。

    推荐阅读

    深入理解C++中的锁

    C++惯用法之RAII思想: 资源管理

    Qt之条件变量QWaitCondition详解(从使用到原理分析全)

    本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/79649.shtml

    如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

    相关文章

    Nvidia显卡架构演进

    1 简介 显示卡&#xff08;英语&#xff1a;Display Card&#xff09;简称显卡&#xff0c;也称图形卡&#xff08;Graphics Card&#xff09;&#xff0c;是个人电脑上以图形处理器&#xff08;GPU&#xff09;为核心的扩展卡&#xff0c;用途是提供中央处理器以外的微处理器帮…

    下载electron 22.3.27 源码错误集锦

    下载步骤同 electron源码下载及编译_electron源码编译-CSDN博客 问题1 从github 下载 dugite超时&#xff0c;原因没有找到 Validation failed. Expected 8ea2d0d3c9d9e4615069913207371ffe892dc10fb93975972f2f6e668f2e3b3a but got e3b0c44298fc1c149afbf4c8996fb92427ae41e…

    洛谷P1120 小木棍

    #算法/进阶搜索 思路: 首先,最初始想法,将我们需要枚举的长木棍个数计算出来,在dfs中,我们先判断,此时枚举这根长木棍需要的长度是否为0,如果为0,我们就枚举下一个根木棍,接着再判断,此时仍需要枚举的木棍个数是否为0,如果为0,代表我们这种方案可行,直接打印长木棍长度,接着我们…

    Linux教程-常用命令系列二

    文章目录 1. 系统管理常用命令1. useradd - 创建用户账户功能基本用法常用选项示例 2. passwd - 管理用户密码功能基本用法常用选项示例 3. kill - 终止进程功能基本用法常用信号示例 4. date - 显示和设置系统时间功能基本用法常用选项时间格式示例 5. bc - 高精度计算器功能基…

    18、TimeDiff论文笔记

    TimeDiff **1. 背景与动机****2. 扩散模型基础****3. TimeDiff 模型****3.1 前向扩散过程****3.2 后向去噪过程** 4、TimeDiff&#xff08;架构&#xff09;原理训练推理其他关键点解释 DDPM&#xff08;相关数学&#xff09;1、正态分布2、条件概率1. **与多个条件相关**&…

    整合SSM——(SpringMVC+Spring+Mybatis)

    目录 SSM整合 创建项目 导入依赖 配置文件 SpringConfig MyBatisConfig JdbcConfig ServletConfig SpringMvcConfig 功能模块 测试 业务层接口测试 控制层测试 SSM是Java Web开发中常用的三个主流框架组合的缩写&#xff0c;分别对应Spring、Spring MVC、MyBatis…

    P1042【深基8,例1】乒乓球

    【题目背景】国际乒联现在主席沙拉拉自从上任以来就立志于推行一系列改革&#xff0c;以推动乒乓球运动在全球的普及。其中 11 分制改革引起了很大的争议&#xff0c;有一部分球员因为无法适应新规则只能选择退役。华华就是其中一位&#xff0c;他退役之后走上了乒乓球研究工作…

    ubuntu24.04上使用qemu和buildroot模拟vexpress-ca9开发板构建嵌入式arm linux环境

    1 准备工作 1.1 安装qemu 在ubuntu系统中使用以下命令安装qemu。 sudo apt install qemu-system-arm 安装完毕后&#xff0c;在终端输入: qemu- 后按TAB键&#xff0c;弹出下列命令证明安装成功。 1.2 安装arm交叉编译工具链 sudo apt install gcc-arm-linux-gnueabihf 安装之…

    用 R 语言打造交互式叙事地图:讲述黄河源区生态变化的故事

    目录 🌟 项目背景:黄河源头的生态变迁 🧰 技术栈介绍 🗺️ 最终效果预览 💻 项目构建步骤 1️⃣ 数据准备 2️⃣ 构建 Leaflet 地图 3️⃣ 使用 scrollama 实现滚动触发事件 4️⃣ 使用 R Markdown / Quarto 打包发布 🎬 效果展示截图 📦 完整代码仓库 …

    CTF--秋名山车神

    一、原网页&#xff1a; 二、步骤&#xff1a; 1.尝试用计算器计算&#xff1a; 计算器溢出&#xff0c;无法正常计算 2.使用python计算&#xff1a; 得出计算结果为&#xff1a;1864710043732437134701060769 3.多次刷新页面&#xff1a; 发现变量为value&#xff0c;要用pos…

    CRC实战宝典:从原理到代码,全面攻克循环冗余校验

    CRC实战宝典&#xff1a;从原理到代码&#xff0c;全面攻克循环冗余校验 github开源&#xff1a;CRC软硬件协同测试项目 CRC 简介 CRC&#xff08;循环冗余校验&#xff09;是一种强大的错误检测技术&#xff0c;广泛应用于数字网络和存储系统。它是确保数据完整性的重要方法…

    【大模型】DeepSeek + Coze 打造个人专属AI智能体使用详解

    目录 一、前言 二、AI智能体介绍 2.1 什么是AI智能体 2.2 AI智能体核心能力 2.3 AI智能应用场景 三、coze 介绍 3.1 coze是什么 3.1.1 平台概述 3.1.2 平台适用人群 3.2 平台核心功能 3.3 coze可以做什么 3.4 为什么选择coze 四、coze 搭建AI智能体操作实践 4.1 搭…

    MySQL入门:数据表的创建

    ​今天我们来介绍一下除HTML外的另一种语言&#xff1a;MySQL语言&#xff1b; MySQL&#xff1a;即一种用于管理和处理关系数据库的标准语言。要用于执行查询、更新、管理数据库中的数据以及定义和操作数据库结构。 接下来我会逐一介绍它的作用以及其中数据表&#xff0c;数据…

    [图论]生成树 引言

    生成树 引言 生成树&#xff1a;一个连通图的生成树是该图的一个极小连通子图。生成树中含有图中全部(设 V V V个)顶点及构成一棵树的 V − 1 V-1 V−1条边&#xff0c;且生成树中不应有环。最小生成树(MST)&#xff1a;图的所有生成树中&#xff0c;边权之和最小的生成树。显…

    AI调试工具有哪些?

    一、深度学习框架专用调试工具 TensorBoard • 功能&#xff1a;实时监控训练指标&#xff08;损失值、准确率&#xff09;、可视化神经网络结构、分析参数分布和梯度信息 • 适用框架&#xff1a;TensorFlow、PyTorch&#xff08;通过插件&#xff09; • 特点&#xff1a;支持…

    深入理解 MCP 协议:开启 AI 交互新时代

    深入理解 MCP 协议&#xff1a;开启 AI 交互新时代&#x1f680; 在当今人工智能蓬勃发展的时代&#x1f310;&#xff0c;大型语言模型&#xff08;LLM&#xff09;已经在众多领域展现出了强大的能力&#xff0c;令人惊叹&#x1f44f;&#xff01;然而&#xff0c;传统的 LLM…

    微信、抖音、小红书emoji符号大全

    1、Emoji 日常符号 &#x1f463;&#x1f440;&#x1f441;️&#x1f444;&#x1f48b;&#x1f442;&#x1f9bb;&#x1f443;&#x1f445;&#x1f9e0;&#x1fac0;&#x1fac1;&#x1f9b7;&#x1f9b4;&#x1f4aa;&#x1f9be;&#x1f9bf;&#x1f9b5;&a…

    【嵌入式】——Linux系统远程操作和程序编译

    目录 一、虚拟机配置网络设置 二、使用PuTTY登录新建的账户 1、在ubuntu下开启ssh服务 2、使用PuTTY连接 三、树莓派实现远程登录 四、树莓派使用VNC viewer登录 五、Linux使用talk聊天程序 1、使用linux自带的talk命令 2、使用c语言编写一个talk程序 一、虚拟机配置网络…

    春和景明-C语言简单代码

    题目要求&#xff1a; 请在centOS Linux中编写一个C语言程序实现如下功能&#xff1a; 同时创建100个用户&#xff0c;用户的账户名称为&#xff1a;Student01 Student02 … Student100;设置每个用户的初始密码为&#xff1a;stud123456请用gcc编译C的源代码&#xff0c;生…

    设计模式之工厂模式(factory pattern):在商品对象创建系统中的应用

    目录 一、设计思路 1. 简单工厂模式 2. 工厂方法模式 3. 抽象工厂模式 二、UML类图&#xff08;PlantUML格式&#xff09; 1.简单工厂模式 2.工厂方法模式 3.抽象工厂模式 三、实现过程与结果 1. 简单工厂模式 2. 工厂方法模式 3. 抽象工厂模式 四、总结 在面向对…