关于c++中锁的种类与使用

关于c++中锁的种类与使用

Optimized tool selectionC++ 标准库中有多种锁管理机制,各有不同的特性和适用场景:

看这段代码中使用的是 std::lock_guard,这是一个自动管理锁的工具。锁会在以下时刻释放:

锁的生命周期

cpp 复制代码
.....
static std::mutex g_mutex;           // 保护 g_control / g_reply 的并发访问
.....
void handleControlGram(const ControlGram &cg) {
    std::lock_guard<std::mutex> lk(g_mutex);  // ← 获取锁
    g_control.SteeringThrottle = (uint8_t)cg.SteeringThrottle;
    // ... 其他赋值操作
    g_control.ControlMode = cg.ControlMode;
}  // ← 函数结束时,lk 对象析构,锁自动释放

锁的释放时机

  • 函数返回时 --- lock_guard 对象超出作用域,析构函数被调用,锁自动释放
  • 无需手动调用 unlock()

为什么使用 lock_guard?

这是 RAII 模式(Resource Acquisition Is Initialization)的应用:

情形 手动 lock/unlock lock_guard
正常执行 ✓ 释放 ✓ 自动释放
异常抛出 锁泄露 ✓ 析构仍会释放
逻辑分支 ✗ 容易遗漏 ✓ 自动保证

在 C++ 中, 是解决多线程数据竞争 的核心工具(C++11 及以后标准提供了完善的锁机制)。针对你的 NVIDIA Orin(Ubuntu20.04,ARM 架构),所有标准 C++ 锁都可以直接使用,编译时仅需链接线程库(-pthread)。

我会分 底层互斥体(锁本体)RAII 自动锁管理工具(推荐必用) 两部分讲解,包含种类、特性、区别、使用场景、代码示例,最后给避坑指南。


一、前置核心知识

  1. 作用 :保护共享资源(变量、文件、硬件接口),保证同一时间只有一个/指定线程访问,避免数据错乱。

  2. 核心头文件

    cpp 复制代码
    #include <mutex>        // C++11 基础锁
    #include <shared_mutex> // C++17 读写锁
    #include <thread>       // 线程
  3. 黄金原则永远用 RAII 自动管理锁 ,不要手动 lock()/unlock()(极易忘记解锁导致死锁)。

  4. 编译命令 (Orin 通用):

    bash 复制代码
    g++ -std=c++17 代码.cpp -o 程序 -pthread

二、第一部分:C++ 标准底层互斥体(锁的本体)

这是锁的核心实现,分为 独占锁、递归锁、超时锁、读写锁 4 大类,共 5 种:

互斥体类型 独占性 递归 超时 共享读写 C++ 版本 核心特性
std::mutex ✅ 独占 C++11 最基础、性能最高
std::recursive_mutex ✅ 独占 C++11 同一线程可重复加锁
std::timed_mutex ✅ 独占 C++11 可尝试加锁,超时放弃
std::recursive_timed_mutex ✅ 独占 C++11 递归+超时
std::shared_mutex ✅ 独占 ✅ 读写分离 C++17 读共享、写独占

1. std::mutex(基础独占锁)

  • 特性独占排他锁 ,同一时间只能被一个线程持有;不可递归、不可超时、不可共享
  • 适用场景:90% 的常规多线程场景(简单共享变量保护)。
  • 禁止操作:同一个线程不能加锁两次(直接死锁)。

2. std::recursive_mutex(递归独占锁)

  • 特性 :允许同一个线程多次加锁 ,必须解锁相同次数才会释放;其他特性同 mutex
  • 适用场景递归函数、嵌套调用中需要加锁的场景(极少用,能不用就不用)。

3. std::timed_mutex(超时独占锁)

  • 特性 :支持超时尝试加锁,不会永久阻塞线程。
  • APItry_lock_for(时间)try_lock_until(时间点)
  • 适用场景:需要避免永久死等锁的场景(如硬件通信、超时任务)。

4. std::shared_mutex(读写锁 / 共享互斥锁,C++17)

  • 核心特性读写分离
    • 读模式(共享):多个线程可以同时加锁读数据;
    • 写模式(独占):只有一个线程能加锁写数据,写时禁止读。
  • 适用场景读多写少(如配置读取、日志、缓存),性能远高于普通独占锁。

三、第二部分:RAII 自动锁管理工具(必用!)

直接操作底层互斥体容易忘记解锁,C++ 提供了 4 种自动锁工具,构造时加锁,析构时自动解锁

管理工具 配合锁类型 手动解锁 超时支持 多锁同时加锁 核心用途
std::lock_guard 所有独占锁 最简单、最常用(默认首选)
std::unique_lock 所有独占锁 灵活锁(配合条件变量、手动控制)
std::shared_lock shared_mutex 读写锁的读模式(共享)
std::scoped_lock 所有锁 C++17 同时锁多个锁,防死锁

四、完整使用示例(可直接在 Orin 上运行)

示例 1:基础用法 std::mutex + lock_guard(首选)

最常用,保护简单共享变量:

cpp 复制代码
#include <iostream>
#include <mutex>
#include <thread>
using namespace std;

mutex mtx;           // 全局互斥锁
int share_num = 0;   // 共享资源

void add_num() {
    // 构造自动加锁,离开作用域自动解锁
    lock_guard<mutex> lock(mtx); 
    share_num++;
    cout << "线程ID: " << this_thread::get_id() << " 数值: " << share_num << endl;
}

int main() {
    thread t1(add_num);
    thread t2(add_num);
    t1.join();
    t2.join();
    return 0;
}

示例 2:递归锁 recursive_mutex

解决递归函数加锁问题:

cpp 复制代码
#include <mutex>
recursive_mutex rmtx;

void func(int n) {
    lock_guard<recursive_mutex> lock(rmtx);
    if (n > 0) func(n-1); // 同一线程重复加锁,无死锁
}

示例 3:超时锁 timed_mutex + unique_lock

避免永久阻塞:

cpp 复制代码
#include <mutex>
#include <chrono>
timed_mutex tmtx;

void try_lock_func() {
    // 尝试加锁,最多等待 100ms
    unique_lock<timed_mutex> lock(tmtx, chrono::milliseconds(100));
    if (lock.owns_lock()) {
        cout << "加锁成功" << endl;
    } else {
        cout << "加锁超时" << endl;
    }
}

示例 4:读写锁 shared_mutex(读多写少最优)

cpp 复制代码
#include <shared_mutex>
shared_mutex s_mtx;
int data = 0;

// 读线程:共享加锁,多个线程可同时读
void read_data() {
    shared_lock<shared_mutex> lock(s_mtx); 
    cout << "读取数据: " << data << endl;
}

// 写线程:独占加锁,同一时间只能一个写
void write_data(int val) {
    unique_lock<shared_mutex> lock(s_mtx); 
    data = val;
    cout << "写入数据: " << data << endl;
}

示例 5:多锁防死锁 scoped_lock(C++17)

同时加多个锁,避免死锁:

cpp 复制代码
mutex m1, m2;
void safe_lock() {
    // 一次性加锁 m1 和 m2,自动避免死锁
    scoped_lock lock(m1, m2); 
}

五、关键区别与选型指南(核心总结)

1. 独占锁 vs 读写锁

  • 独占锁(mutex) :读写都互斥,适合读写频率差不多的场景;
  • 读写锁(shared_mutex) :读不互斥、写互斥,适合读多写少(性能提升 5~10 倍)。

2. lock_guard vs unique_lock

  • lock_guard :轻量、无额外开销,日常开发首选
  • unique_lock:灵活(可手动解锁、超时、配合条件变量),有微小开销。

3. 普通锁 vs 递归锁

  • 99% 的场景不要用递归锁,它会掩盖代码设计问题,仅用于递归函数。

六、避坑指南(Orin 开发必看)

  1. 绝对不要手动调用 lock()/unlock()
    异常、提前 return 都会导致锁无法释放,直接死锁。
  2. 锁粒度尽可能小
    只保护共享资源,不要在锁内执行耗时操作(如网络、打印)。
  3. 避免死锁
    • 多锁必须用 scoped_lock 同时加锁;
    • 不要循环等待锁。
  4. ARM 平台(Orin)优化建议
    短临界区(几行代码)优先用 自旋锁std::spin_lock C++20),避免线程切换开销。

七、快速选型口诀

  1. 常规场景 → mutex + lock_guard
  2. 读多写少 → shared_mutex + shared_lock
  3. 需要灵活控制/条件变量 → unique_lock
  4. 多锁防死锁 → scoped_lock
  5. 递归调用 → recursive_mutex
  6. 不想永久等待 → timed_mutex

总结

  1. C++ 锁分底层互斥体 (5种)和 RAII 管理工具 (4种),永远用 RAII 自动管理
  2. 基础开发用 std::mutex + std::lock_guard 足够;
  3. 读多写少用读写锁,性能最优;
  4. Orin(ARM)编译加 -std=c++17 -pthread,代码全平台通用。
相关推荐
凯瑟琳.奥古斯特1 小时前
懒加载技巧优化栈增减操作(力扣3629)
开发语言·数据结构·算法
hans汉斯1 小时前
基于LSTM与扩展卡尔曼滤波的无人机机载电子磁干扰补偿研究
开发语言·人工智能·算法·目标检测·lstm·人机交互·无人机
凤凰院凶涛QAQ1 小时前
《C++转Java快速入手系列》继承与多态篇
java·c++
倒霉蛋小马1 小时前
Idea--如何同一个SpringBoot项目复制多次,模拟集群环境
java·ide·intellij-idea
赏金术士1 小时前
Kotlin 从入门到进阶 之Lambda & 集合高阶模块(四)
开发语言·windows·kotlin
IT 行者1 小时前
Spring Boot 4.1.0-RC1 发布:核心新特性解析
java·spring boot·后端
yingjie1101 小时前
用mcc编译的MATLAB EXE被反编译了?这个工具能帮你加固
开发语言·matlab
Cat_Rocky1 小时前
Ingress-Nginx 全局超时配置及生效方式
java·服务器·nginx
Evand J1 小时前
【MATLAB绘图】三维曲面与二维映射组合图绘制,进阶教程与代码示例
开发语言·matlab·绘图