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

C++学习:六个月从基础到就业——多线程编程:互斥量与锁

C++学习:六个月从基础到就业——多线程编程:互斥量与锁

本文是我C++学习之旅系列的第五十五篇技术文章,也是第四阶段"并发与高级主题"的第二篇,介绍C++11及以后标准中的互斥量(mutex)、锁(lock)等线程同步基础。查看完整系列目录了解更多内容。

引言

多线程程序中,多个线程对共享资源的访问会导致数据竞争和不确定行为。为保证数据一致性和线程安全,C++标准库提供了多种同步原语,其中最基础的就是互斥量(std::mutex)和各种锁(std::lock_guardstd::unique_lock等)。本篇将系统讲解C++中的互斥量与锁的用法、原理及常见陷阱。

目录

  • 多线程编程:互斥量与锁
    • 引言
    • 目录
    • 互斥量基础
      • std::mutex的基本用法
      • std::lock_guard与RAII
      • std::unique_lock与灵活锁管理
      • 递归互斥量std::recursive_mutex
      • 定时锁定std::timed_mutex
    • 多锁与死锁预防
      • std::lock和std::scoped_lock
      • 避免死锁的常用策略
    • 读写锁(共享互斥量)
      • std::shared_mutex与std::shared_lock
    • 实际应用案例
      • 线程安全的计数器
      • 线程安全的队列
    • 常见问题与陷阱
      • 死锁与活锁
      • 锁粒度与性能
      • 异常安全与锁
    • 总结

互斥量基础

std::mutex的基本用法

std::mutex是C++11标准库提供的最基础的互斥量类型。它用于保护临界区,确保同一时刻只有一个线程可以访问共享资源。

#include <iostream>
#include <thread>
#include <mutex>int counter = 0;
std::mutex counterMutex;void increment(int times) {for (int i = 0; i < times; ++i) {counterMutex.lock();++counter;counterMutex.unlock();}
}int main() {std::thread t1(increment, 100000);std::thread t2(increment, 100000);t1.join();t2.join();std::cout << "Final counter: " << counter << std::endl;return 0;
}

手动调用lock()unlock()容易出错,推荐使用RAII风格的锁管理。

std::lock_guard与RAII

std::lock_guard是最简单的RAII锁管理器,构造时加锁,析构时自动解锁,适合简单场景。

#include <mutex>std::mutex mtx;void safeFunction() {std::lock_guard<std::mutex> lock(mtx); // 构造时加锁// 临界区
} // 离开作用域自动解锁

优点:

  • 避免忘记解锁
  • 异常安全

std::unique_lock与灵活锁管理

std::unique_locklock_guard更灵活,支持延迟加锁、提前解锁、锁的转移等。

#include <mutex>std::mutex mtx;void flexibleFunction() {std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 不立即加锁// ...做一些准备...lock.lock(); // 手动加锁// ...临界区...lock.unlock(); // 手动解锁// ...非临界区...lock.lock(); // 重新加锁// ...再次进入临界区...
} // 离开作用域自动解锁(如果还持有锁)

unique_lock还可以与std::lock配合实现多互斥量的安全加锁。

递归互斥量std::recursive_mutex

递归互斥量允许同一线程多次获得同一个锁,适用于递归调用场景。

#include <mutex>std::recursive_mutex recMtx;void recursiveFunc(int n) {if (n <= 0) return;std::lock_guard<std::recursive_mutex> lock(recMtx);// ...临界区...recursiveFunc(n - 1); // 递归调用
}

注意:递归互斥量开销更大,非必要时应避免使用。

定时锁定std::timed_mutex

std::timed_mutexstd::recursive_timed_mutex支持超时锁定:

#include <mutex>
#include <chrono>
#include <iostream>std::timed_mutex tmtx;void tryLockWithTimeout() {if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {std::cout << "Lock acquired" << std::endl;tmtx.unlock();} else {std::cout << "Timeout, lock not acquired" << std::endl;}
}

多锁与死锁预防

std::lock和std::scoped_lock

std::lock可以一次性安全地锁定多个互斥量,避免死锁。C++17引入了std::scoped_lock,更简洁:

#include <mutex>std::mutex m1, m2;void safeMultiLock() {std::scoped_lock lock(m1, m2); // 同时加锁,顺序无关// ...临界区...
}

避免死锁的常用策略

  1. 始终以相同顺序加锁
  2. 使用std::lock/std::scoped_lock
  3. 尽量缩小锁的作用域
  4. 避免在持锁时调用用户回调

读写锁(共享互斥量)

std::shared_mutex与std::shared_lock

C++17引入了std::shared_mutex,允许多个线程同时读,但写时独占。

#include <shared_mutex>
#include <thread>
#include <iostream>std::shared_mutex rwMutex;
int sharedData = 0;void reader() {std::shared_lock lock(rwMutex);std::cout << "Read: " << sharedData << std::endl;
}void writer(int value) {std::unique_lock lock(rwMutex);sharedData = value;std::cout << "Write: " << sharedData << std::endl;
}

适用于读多写少的场景。

实际应用案例

线程安全的计数器

#include <mutex>class SafeCounter {int value = 0;std::mutex mtx;
public:void increment() {std::lock_guard<std::mutex> lock(mtx);++value;}int get() {std::lock_guard<std::mutex> lock(mtx);return value;}
};

线程安全的队列

#include <queue>
#include <mutex>
#include <condition_variable>
#include <optional>template<typename T>
class ThreadSafeQueue {std::queue<T> q;std::mutex mtx;std::condition_variable cv;
public:void push(T value) {{std::lock_guard<std::mutex> lock(mtx);q.push(std::move(value));}cv.notify_one();}std::optional<T> pop() {std::unique_lock<std::mutex> lock(mtx);if (q.empty()) return std::nullopt;T value = std::move(q.front());q.pop();return value;}// 支持阻塞等待T wait_and_pop() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [this]{ return !q.empty(); });T value = std::move(q.front());q.pop();return value;}
};

常见问题与陷阱

死锁与活锁

  • 死锁:多个线程互相等待对方释放锁,导致程序卡死。
  • 活锁:线程不断尝试获取锁但始终失败,导致无进展。

避免方法:使用std::lock/std::scoped_lock,保持加锁顺序一致,缩小锁粒度。

锁粒度与性能

  • 粗粒度锁:简单但可能降低并发性能
  • 细粒度锁:提高并发,但设计复杂,易出错

建议:根据实际需求权衡,优先保证正确性。

异常安全与锁

RAII锁(如lock_guardunique_lock)能确保异常发生时自动释放锁,避免死锁。

总结

互斥量和锁是C++多线程同步的基础。合理使用std::mutexstd::lock_guardstd::unique_lockstd::scoped_lock等工具,可以有效避免数据竞争和死锁问题。C++17/20引入的shared_mutexscoped_lock等进一步提升了并发性能和代码简洁性。

在下一篇文章中,我们将继续学习条件变量(std::condition_variable),它是实现线程间通信和同步的关键工具。


这是我C++学习之旅系列的第五十五篇技术文章。查看完整系列目录了解更多内容。

http://www.xdnf.cn/news/551197.html

相关文章:

  • Git 删除大文件教程
  • 如果用户点击微博的关注图标,但是app上面没有反应,应该怎么排查这个问题?
  • 集成飞书多维表格
  • 详解MySQL 的 binlog,redo log,undo log
  • 【razor】pacing平滑发送及调度机制分析
  • 物联网低功耗保活协同优化方案:软硬件与WiFi网关动态联动
  • MySQL--day4--排序与分页
  • 学习vue3:监听器
  • RK3588 IREE+Vulkan ResNet50推理测试
  • @ColorRes和@ColorInt什么区别
  • 基于天猫 API 的高效商品详情页实时数据接入方法解析
  • 2025年 全国青少年信息素养大赛 算法创意挑战赛C++ 小学组 初赛真题
  • 鸿蒙开发进阶:深入解析ArkTS语言特性与开发范式
  • GitHub 趋势日报 (2025年05月19日)
  • windows使用anaconda安装pytorch cuda版本
  • 倾斜摄影已过时?3DGS能否重塑三维重建效率天花板
  • 寻找最优美做题曲线
  • Linux在防火墙中添加开放端口
  • 爱普生Epson PX-S887打印机信息
  • AI能源危机:人工智能发展与环境可持续性的矛盾与解决之道
  • 计算机视觉与深度学习 | PSO-MVMD粒子群算法优化多元变分模态分解(Matlab完整代码和数据)
  • Git报错:Another git process seems to be running in this repository
  • C及C++不定参数log输出方法(printf风格)
  • Python训练打卡Day29
  • Latex写作中文版
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月20日第83弹
  • C# 类的基本概念(程序和类:一个简单的示例)
  • web基础
  • 信奥赛-刷题笔记-栈篇-T3-P4387验证栈序列0520
  • Web技术与Nginx网站环境