当前位置: 首页 > 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++学习之旅系列的第五十五篇技术文章。查看完整系列目录了解更多内容。

相关文章:

  • Awesome ChatGPT Prompts:释放AI对话潜力的开源利器
  • Apache Apisix配置ip-restriction插件以限制IP地址访问
  • 【数据结构】
  • python-leetcode 68.有效的括号
  • 在Java项目中集成Deepseek大语言模型实践指南
  • AliSQL:阿里巴巴开源数据库的技术革新与应用实践
  • MySQL高可用之ProxySQL + MGR 实现读写分离实战
  • jmeter转义unicode变成中文
  • JMeter 教程:JSON 断言的简单介绍
  • 当PLC遇上电焊机器人:EtherCAT转CANopen上演工业级“语言翻译官”
  • Spring AI(7)——RAG
  • tigase源码学习笔记-事件总线EventBus
  • Pichome 开源网盘程序index.php 文件读取漏洞(CVE-2025-1743)
  • 【25软考网工】第七章 (2)UOS Linux文件和目录管理、用户和组管理
  • 破解 PCB 制造四大痛点:MOM 系统构建智能工厂新范式
  • SSRF(服务器端请求伪造)基本原理靶场实现
  • Java 02入门,封装继承多态
  • 哈希查找方法
  • Oracle RAC 中的 RBAL 进程
  • Android SharedPreferences:从零到一的全面解析与实战指南
  • 重庆一男大学生掉进化粪池死亡,重庆对外经贸学院:以学校通报为准
  • 国家发改委:大部分稳就业稳经济政策将在6月底前落地
  • 8000余万元黄金投入研发后“不知去向”,咋回事?
  • 人民日报评论员观察:稳就业,抓好存量、增量、质量
  • 中共中央、国务院印发《党政机关厉行节约反对浪费条例》
  • 江苏疾控:下设部门无“病毒研究所”,常荣山非本单位工作人员