当前位置: 首页 > ds >正文

C++ 并发编程:全面解析主流锁管理类

在 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.xdnf.cn/news/19324.html

相关文章:

  • Day17_【机器学习—特征预处理(归一化和标准化)】
  • Unity学习----【数据持久化】二进制存储(一)
  • 仿真高斯光束同时分析光纤耦合特点并仿真
  • 大模型入门学习微调实战:基于PyTorch和Hugging Face电影评价情感分析模型微调全流程(附完整代码)手把手教你做
  • Lenovo C225 一体机拆机维修教程
  • 从零开始学Shell编程:从基础到实战案例
  • 【完整源码+数据集+部署教程】骨折检测系统源码和数据集:改进yolo11-EfficientHead
  • flume事务机制详解:保障数据可靠性的核心逻辑
  • Vue3 kkfileview 的使用
  • 第八章 惊喜01 测试筹备会
  • Shell 中 ()、(())、[]、{} 的用法详解
  • ros2--service/服务--接口
  • Redis不同场景下的注意事项
  • C++中自由函数(free function)概念
  • 比随机森林更快更强?极限森林的核心逻辑与完整实践指南
  • 零知识证明的刑事证据困境:隐私权与侦查权的数字博弈
  • Hal aidl 模板
  • open webui源码分析12-Pipeline
  • 用docker安装rstudio-server
  • 【python开发123】三维地球应用开发方案
  • Adobe Acrobat 中通过 JavaScript 调用 Web 服务
  • ros、slam、激光雷达、自动驾驶相关学习内容和计划
  • 深度拆解判别式推荐大模型RankGPT!生成式精排落地提速94.8%,冷启动效果飙升,还解决了传统推荐3大痛点
  • Pointer--Learing MOOC-C语言第九周指针
  • “北店南下”热潮不减,企业赴港开拓业务如何站稳脚跟
  • springboot java开发的rocketmq 事务消息保证
  • SyncBack 安全备份: 加密文件名及文件内容, 防止黑客及未授权的访问
  • Ansible Playbook 实践
  • CPP学习之map和set
  • 99.数据大小端模式