在 C++ 的并发世界里,管理共享资源就像是在一个繁忙的十字路口指挥交通。如果指挥不当,就会发生混乱甚至致命的“死锁”。C++ 标准库提供的各种锁管理工具,就是我们手中的“交通信号灯”,它们各自拥有独特的职能,帮助我们编写出安全、高效且优雅的多线程代码。

1. std::lock_guard:忠诚的卫士

std::lock_guard 是最基础、最可靠的守卫。它就像一个忠诚的士兵,一旦被部署(构造),就会牢牢地守住阵地(锁定互斥量),直到任务完成(超出作用域)自动卸下职责。它从不偷懒,也从不犯错,无论程序是正常退出还是因异常而中断,它都确保锁被安全释放。

它的优势在于简单而纯粹:你只需要告诉它要守护哪个互斥量,剩下的它都会为你自动完成。在你的代码中,如果只需要在一个作用域内独占访问一个资源,lock_guard 永远是你的首选,因为它没有多余的开销,也不给你犯错的机会。

  • 核心特性

    • 自动加锁与解锁:遵循 RAII 原则,生命周期与作用域绑定。
    • 不可移动、不可拷贝:确保锁的所有权唯一。
    • 无死锁避免:如果你需要锁定多个互斥量,必须手动确保所有线程都以相同的顺序加锁,否则可能发生死锁。
  • 适用场景

    • 当你需要在一个函数或一个代码块中安全地锁定一个互斥量时。这是最常见的独占锁使用模式。
  • 示例

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <vector>std::mutex mtx;
    int shared_data = 0;void safe_increment() {// 创建 lock_guard 对象,mtx 在这里被锁定std::lock_guard<std::mutex> lock(mtx);// 在此作用域内安全访问共享资源shared_data++;// 当 lock 超出作用域,mtx 会自动解锁
    } // 这里会自动调用 lock.unlock()int main() {std::vector<std::thread> threads;for (int i = 0; i < 10; ++i) {threads.emplace_back(safe_increment);}for (auto& t : threads) {t.join();}std::cout << "最终 shared_data 的值: " << shared_data << std::endl;return 0;
    }
    

2. std::unique_lock:全能的指挥官

std::unique_lock 是一位能力超群的指挥官。它拥有 lock_guard 的所有优点,但其最大的特点是灵活。它不像 lock_guard 那样死板,你可以在任何时候手动加锁、解锁,甚至决定在创建时延迟加锁。这种灵活性使得它能应对更复杂的战术。

unique_lock 的真正力量体现在与 std::condition_variable 的协同作战中。在等待某个条件时,unique_lock 可以优雅地放下手中的锁,让出资源,直到被通知时再迅速重新锁定。这种合作机制是实现生产者-消费者模型、线程池等高级并发模式的核心。

  • 核心特性

    • 灵活的加锁/解锁控制:提供 lock()unlock()try_lock() 等成员函数。
    • 可延迟加锁:通过 std::defer_lock 构造,创建对象时不立即加锁。
    • 可移动:可以作为函数参数或返回值,将锁的所有权转移。
    • 与条件变量配合:是 std::condition_variable::wait() 函数唯一接受的锁类型。
  • 适用场景

    • 当你需要手动控制锁的加锁和解锁时。
    • 当你需要与 std::condition_variable 配合,实现等待/通知机制时。
    • 当你需要将锁的所有权从一个函数转移到另一个函数时。
  • 示例

    #include <iostream>
    #include <thread>
    #include <mutex>
    #include <condition_variable>std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;void worker_thread() {// 创建 unique_lock 对象,并锁定互斥量std::unique_lock<std::mutex> lock(mtx);std::cout << "工作线程正在等待条件..." << std::endl;// 等待条件变为 true,wait() 会原子地释放锁并进入休眠cv.wait(lock, []{ return ready; }); // 被唤醒后,wait() 会自动重新锁定互斥量std::cout << "工作线程被唤醒,开始处理数据。" << std::endl;
    }void main_thread() {std::this_thread::sleep_for(std::chrono::milliseconds(100));// 创建 unique_lock 并锁定互斥量std::unique_lock<std::mutex> lock(mtx);ready = true;std::cout << "主线程设置条件,并通知工作线程。" << std::endl;// 必须在通知前释放锁,以允许被唤醒的线程获取它lock.unlock(); cv.notify_one();
    }int main() {std::thread t1(worker_thread);std::thread t2(main_thread);t1.join();t2.join();return 0;
    }
    

3. std::scoped_lock:智慧的协调者

std::scoped_lock 是 C++17 引入的,它的主要作用是简化多互斥量加锁,并使用内置的死锁避免算法。你可以把它看作是 std::lock_guard 的多功能升级版。

  • 核心特性

    • 同时锁定一个或多个互斥量
    • 内置死锁避免算法:它会以原子方式尝试锁定所有互斥量,如果失败则回滚并重试,确保不会因锁顺序不一致而死锁。
    • 无手动控制:和 lock_guard 一样,不能手动加锁或解锁。
  • 适用场景

    • 当你需要同时锁定多个互斥量时,这是最安全、最方便的选择。它彻底消除了死锁的风险。
  • 示例

    #include <iostream>
    #include <thread>
    #include <mutex>std::mutex mtx1;
    std::mutex mtx2;void transfer_data_safe(int from_id, int to_id) {std::cout << "线程 " << std::this_thread::get_id() << " 正在尝试数据传输..." << std::endl;// 一次性锁定 mtx1 和 mtx2,使用内置的死锁避免算法// 无论哪个线程先获得哪个锁,都保证不会发生死锁std::scoped_lock lock(mtx1, mtx2);// 模拟数据传输std::this_thread::sleep_for(std::chrono::milliseconds(50));std::cout << "线程 " << std::this_thread::get_id() << " 安全地完成了数据传输。" << std::endl;
    } // lock 超出作用域,mtx1 和 mtx2 自动解锁int main() {std::thread t1(transfer_data_safe, 1, 2);std::thread t2(transfer_data_safe, 2, 1);t1.join();t2.join();return 0;
    }
    

4. std::shared_lock:高效的图书馆管理员

std::shared_lock 是 C++14 引入的,它与 std::shared_mutex(也叫读写锁)配合使用。它的主要作用是管理共享锁的所有权,提供一种允许多个线程同时读取,但只允许一个线程写入的同步机制。

  • 核心特性

    • 共享锁模式:允许多个线程同时持有锁,用于并发读取。
    • RAII 风格:在构造时加锁,在析构时自动解锁。
    • 必须与 std::shared_mutex 配合使用
  • 适用场景

    • 读多写少的场景。当读取数据的频率远高于写入数据时,使用 shared_lock 可以显著提高程序的并发性能。
  • 示例

    #include <iostream>
    #include <thread>
    #include <shared_mutex>
    #include <vector>
    #include <string>std::string shared_data = "initial";
    std::shared_mutex shared_mtx;// 读者线程:只读取数据,可以并发执行
    void reader_thread(int id) {// 构造 shared_lock 时,请求共享锁std::shared_lock<std::shared_mutex> lock(shared_mtx);std::cout << "读者 " << id << " 正在读取: " << shared_data << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(20));
    } // lock 超出作用域,自动释放共享锁// 写者线程:修改数据,独占访问
    void writer_thread(const std::string& new_data) {// 构造 unique_lock 时,请求独占锁,会阻塞所有读者和写者std::unique_lock<std::shared_mutex> lock(shared_mtx);std::cout << "写者正在写入..." << std::endl;shared_data = new_data;std::this_thread::sleep_for(std::chrono::milliseconds(50));
    } // lock 超出作用域,自动释放独占锁int main() {std::vector<std::thread> readers;for (int i = 0; i < 5; ++i) {readers.emplace_back(reader_thread, i);}std::thread writer1(writer_thread, "new data");for (int i = 5; i < 10; ++i) {readers.emplace_back(reader_thread, i);}writer1.join();for (auto& t : readers) {t.join();}std::cout << "最终数据是: " << shared_data << std::endl;return 0;
    }
    

5. std::lock:传统的锁定大师

std::lock 是一个函数,而不是一个类。它的主要作用是一次性锁定多个互斥量,并使用内置的死锁避免算法

  • 核心特性

    • 函数:不是 RAII 类,通常需要与 std::unique_lockstd::defer_lock 配合使用。
    • 死锁避免:通过其内部的算法,确保无论传入互斥量的顺序如何,都能安全地加锁。
  • 适用场景

    • 在 C++11/14 版本中,是处理多锁死锁问题的标准方法。在 C++17 及以后,通常被 std::scoped_lock 所取代。
  • 示例

    #include <iostream>
    #include <thread>
    #include <mutex>std::mutex mtxA;
    std::mutex mtxB;void process_data_lock_function() {std::cout << "线程 " << std::this_thread::get_id() << " 正在处理数据..." << std::endl;// 延迟加锁,创建 unique_lock 对象但不立即锁定互斥量std::unique_lock<std::mutex> lockA(mtxA, std::defer_lock);std::unique_lock<std::mutex> lockB(mtxB, std::defer_lock);// 使用 std::lock 函数一次性锁定两个互斥量std::lock(lockA, lockB);std::cout << "线程 " << std::this_thread::get_id() << " 已安全地获取所有锁。" << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(100));// lockA 和 lockB 超出作用域时会自动解锁
    }int main() {std::thread t1(process_data_lock_function);std::thread t2(process_data_lock_function);t1.join();t2.join();return 0;
    }
    

总结对比

特性std::lock_guardstd::unique_lockstd::scoped_lockstd::shared_lock
锁定类型独占锁独占锁独占锁共享锁
加锁数量111或多个1
灵活性最低最高中等
死锁避免内置
与条件变量
主要用途简单、安全的单锁管理需要灵活控制锁或与条件变量配合安全的多锁管理读多写少场景

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

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

相关文章

Spring boot 启用第二数据源

1. 数据源配置代码&#xff1a;import com.alibaba.druid.pool.DruidDataSource; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilde…

Wi-Fi 时延与掉包的关键因素全解析

在无线网络性能优化中&#xff0c;时延&#xff08;Latency&#xff09;与掉包&#xff08;Packet Loss&#xff09;是最核心的两个指标。本文将从 物理层、MAC层、系统栈、业务形态与环境 等多个维度&#xff0c;对 Wi-Fi 时延与掉包的关键因素进行全面梳理&#xff0c;并结合…

《Python 文本分析实战:从单词频率统计到高效可视化的全流程指南》

《Python 文本分析实战:从单词频率统计到高效可视化的全流程指南》 一、引言:小任务背后的大世界 在我多年的开发与教学中,文本处理始终是一个绕不开的主题。无论是日志分析、自然语言处理,还是搜索引擎、推荐系统,几乎所有数据驱动的系统都离不开对文本的理解。而“统计…

12KM无人机高清图传通信模组——打造未来空中通信新高度

在无人机技术飞速发展的今天&#xff0c;高清图传和稳定的通信模块无疑是提高无人机作业效率和可靠性的关键因素。作为新一代无人机核心技术之一&#xff0c;深圳云望物联12KM无人机高清图传通信模组凭借其卓越的性能&#xff0c;正逐步成为行业内的宠儿&#xff0c;成为无人机…

【LeetCode 热题 100】62. 不同路径——(解法二)递推

Problem: 62. 不同路径 文章目录整体思路完整代码时空复杂度时间复杂度&#xff1a;O(m * n)空间复杂度&#xff1a;O(m * n)整体思路 这段代码同样旨在解决 “不同路径” 问题&#xff0c;但它采用的是一种 自底向上&#xff08;Bottom-Up&#xff09;的动态规划 方法&#x…

C++ 高阶错误解析:MSVC 与 Qt 全景指南

在 C 开发中&#xff0c;尤其是在 Windows 平台使用 MSVC 或 Qt 框架 时&#xff0c;程序员经常会遇到编译错误、链接错误和运行时异常。本文将系统梳理这些问题&#xff0c;按 语法错误、类型错误、链接错误、Qt 运行错误 分类&#xff0c;并给出 触发示例、原因分析及修复策略…

基于Net海洋生态环境保护系统的设计与实现(代码+数据库+LW)

摘要 随着全球气候变化和人类活动的加剧&#xff0c;海洋生态系统面临着前所未有的威胁。污染、过度捕捞、栖息地破坏等问题严重影响了海洋生物多样性和生态平衡。为了应对海洋生态系统面临的严重威胁&#xff0c;如污染、过度捕捞和栖息地破坏等问题&#xff0c;利用C#语言和…

DoIP路由激活报文

目录 DoIP路由激活报文详解 基本概念 报文结构 响应报文 通信流程 注意事项 **DoIP (Diagnostics over Internet Protocol) 报文详解** **1. DoIP 报文结构** **1.1 通用报文格式** **2. 常见 DoIP 报文类型** **3. 典型 DoIP 报文示例** **3.1 车辆识别请求(广播)** **3.2 车…

学习Python中Selenium模块的基本用法(8:元素操作-2)

定位网页元素后&#xff0c;调用is_displayed函数可以判断元素的显示状态&#xff0c;如百度网站中有默认隐藏的元素&#xff0c;此时即可使用is_displayed函数判断该元素的显示状态&#xff0c;如下面代码所示&#xff1a;driver webdriver.Chrome() driver.get("https:…

双指针:从「LC11 盛最多水的容器」到「LC42 接雨水」

LC11 盛最多水的容器 选择两条线&#xff0c;它们与x轴构成的容器可以盛的水量取决于两条线中较短的那条以及两条线之间的距离。 朴素的思想是使用i和j遍历height中的所有线&#xff0c;但是这样的时间复杂度是O(n2)O(n^2)O(n2)。 我们让i从0开始&#xff0c;j从n-1开始&…

WINTRUST!_GetMessage函数分析之CRYPT32!CryptSIPGetSignedDataMsg函数的作用是得到nt5inf.cat的信息

UEDIT打开nt5inf.cat。第一部分&#xff1a;BOOL _GetMessage(CRYPT_PROVIDER_DATA *pProvData) {DWORD dwMsgEncoding;SIP_SUBJECTINFO *pSubjInfo;SIP_DISPATCH_INFO *pSip;DWORD cbEncodedMsg;BYTE *pbEncodedMsg;DWORD …

编译esp32报错解决办法

报错信息&#xff1a;CMake Error at build/CMakeFiles/git-data/grabRef.cmake:48 (file):file failed to open for reading (No such file or directory):这个错误是由于 Git 的安全检查导致的。从错误信息可以看出&#xff0c;Git 检测到了"可疑的所有权"&#xf…

【AI】常见8大LLM大语言模型地址

序号AI名称地址1 ChatGPT &#xff08;OpenAI&#xff09;https://chat.openai.com/2Gemini (Google personal AI assistant)https://gemini.google.com/app3Grok (xAI Grok LLM)https://x.ai/4DeepSeek (DeepSeek AI chatbot)DeepSeek5Claude (Anthropic Claude AI)App unavai…

软件系统的部署方式:单机、主备(冷主备、热主备)、集群

一、单机部署单机部署是将软件系统所有组件&#xff08;应用、数据库等&#xff09;部署在单台服务器上&#xff0c;架构简单、成本低但存在单点故障风险&#xff0c;适用于低负载或测试场景。一台服务器坏了&#xff0c;软件系统无法服务。二、主备&#xff08;冷主备、热主备…

从体验到系统工程丨上手评测国内首款 AI 电商 App

作者&#xff1a;王晨&#xff08;望宸&#xff09; 产品界面&#xff0c;往往体现了产品的设计哲学&#xff0c;界面是产品的第一入口。 近期&#xff0c;1688 推出了 1688 AI App&#xff0c;这貌似是国内第一个电商领域的独立 AI App 应用&#xff08;若不是&#xff0c;欢…

QML QQuickImage: Cannot open: qrc:/images/shrink.png(已解决)

此问题是 在 QT Quick 项目 显示图片的时候 遇到&#xff0c;显示&#xff1a;QML QQuickImage: Cannot open: qrc:/images/shrink.png&#xff0c;不能 打开 图片。为了解决此问题&#xff0c;找了很多资料&#xff0c;虽然是比较简单&#xff0c;但对于初学者来说&#xff0c…

maven scope 详解

Maven 的 scope用于定义依赖项在项目构建生命周期中的可见性和传递性&#xff0c;控制依赖在编译、测试、运行等阶段的可用性及是否被打包到最终产物中。以下是详细解析&#xff1a;⚙️ ​​一、Scope 的核心作用​​​​生命周期控制​​决定依赖在编译、测试、运行阶段的可用…

Python的一次实际应用:利用Python操作Word文档的页码

Python的一次实际应用&#xff1a;利用Python操作Word文档的页码 需求&#xff1a;一次性处理24个文档的页码。 文档详情&#xff1a; 1、每个word文档包含800页左右&#xff0c;每一页包含一个标题和一张图片。 2、由于图片有横排也有竖排&#xff0c;因此&#xff0c;每页文档…

Android15 GKI版本分析Kernel Crash问题

环境介绍编译主机&#xff1a;amd64 Ubuntu 22.04Android源码&#xff1a;Android15 GKIKernel版本&#xff1a;Linux 6.16Android构建系统&#xff1a;bazel构建工具链&#xff1a;gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-定位Linux…

rocky 9部署Zabbix监控

一、rocky安装 需要注意在设置root用户密码时&#xff0c;勾选ssh远程连接 安装完成后直接用root登录 1. 网络配置 输入nmtui 进入网络配置界面 选择 Edit a connection&#xff0c;再选择接口 ens3 IPV4更改为Maual 手动模式 根据实际环境配置IP地址 重启网络 systemctl …