C++11 std::mutex

std::mutex

cpp 复制代码
#include <mutex>
using std::mutex;

互斥量 ,用于在多线程环境下保护共享资源,确保同一时刻只有一个线程 进入临界区,从而避免数据竞争和部分并发导致的未定义行为。

典型场景:多个线程同时读写同一个 std::vector、计数器、对象状态等。

std::mutex 本身:

  • 表达共享资源的 "被锁的状态 + 所有权"
  • 同一时刻最多只能被 一个线程持有

基本用法

cpp 复制代码
std::mutex m;

m.lock();  // m上锁
m.unlock(); // m 解锁
m.try_lock(); // 尝试获取锁,不阻塞;成功返回 `true`,失败返回 `false`。

mutex理解

问题代码示例

cpp 复制代码
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

int main() {
    std::mutex m;

    bool flag = false; // 逻辑检测
    int  counter = 0;

    auto work = [&](int tid) {
        for (int i = 0; i < 3; ++i) {
            m.lock();

            if (flag) {
                std::cout << "[ERROR] tid = " << tid << "提前进入临界区" << std::endl;
            }
            flag = true;
            int before = counter;
            ++counter;
            std::cout << "tid = " << tid << " 获得锁, i = " << i
                      << " counter: " << before << " -> " << counter
                      << std::endl;
            flag = false;
            // m.unlock();
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    };

    std::vector<std::thread> ts;
    for (int t = 0; t < 3; ++t) {
        ts.emplace_back(work, t);
    }
    for (auto& th : ts) {
        th.detach();
    }

    std::this_thread::sleep_for(std::chrono::seconds(2));
    m.unlock(); // 可能导致未定义行为
    std::this_thread::sleep_for(std::chrono::seconds(10));
    std::cout << "final counter = " << counter << "\n";
    return 0;
}
mutex如何工作?

关键点在m变量。

步骤:

  1. m在初始时是unlock
  2. tid_0第一次访问m,成功上锁
  3. tid_0第二次访问m,已经上锁了,导致自死锁。
  4. tid_1第一次访问m,已经上锁了,故线程tid_1挂起,等待 m 解锁。
  5. tid_2第一次访问m,已经上锁了,故线程tid_2挂起,等待 m 解锁。
  6. 主线程中等待了2s后,m解锁,此时调度器选择某个线程执行,而后m再次上锁。
  7. 最后counter = 2。

关键点:

  • 锁的状态是共享的
  • 是否阻塞取决于锁的状态
  • 阻塞是线程级别的,不是程序级别的

关于锁的lock/unlock需要在同一线程,否则会导致未定义行为。
tid_0第二次访问m时,按道理来说是不可能再解锁m,自死锁。

RAII锁管理

手写 lock()/unlock() 容易在:

  • 异常抛出
  • 多分支 return
  • 中途 continue/break 的路径上漏掉 unlock,导致死锁。
    lock_guard / unique_lock 的价值是自动释放,避免漏写导致死锁。
std::lock_guard<std::mutex>
cpp 复制代码
#include <mutex> 
using std::lock_guard;
  • 构造时 lock()
  • 析构时 unlock()
  • 不可手动解锁
  • 零额外状态,最小开销
cpp 复制代码
{
	std::lock_guard<std::mutex> g(m); 
	/**
	 * 临界区
	 */
}
std::unique_lock<std::mutex>
cpp 复制代码
#include <mutex> 
using std::unique_lock;

支持:

  • 延迟加锁
  • 尝试加锁
  • 手动 unlock / re-lock
  • condition_variable
cpp 复制代码
std::unique_lock<std::mutex> lk(m, std::defer_lock); 
lk.lock(); 
// 临界区 
lk.unlock();
状态管理
std::defer_lock

unique_lock可用

构造时 unique_lock 时不调用 m.lock()

需要显式 lk.lock() 或 lk.try_lock()

cpp 复制代码
std::mutex m;

void f() {
    std::unique_lock<std::mutex> lk(m, std::defer_lock);
    
    // prepare();
    lk.lock();
    // 临界区
}
std::try_to_lock

unique_lock可用

构造时等价于执行一次 try_lock():

  • 成功:owns_lock()==true
  • 失败:owns_lock()==false(不会阻塞)
cpp 复制代码
std::mutex m;

void f() {
    std::unique_lock<std::mutex> lk(m, std::try_to_lock);
    if (!lk.owns_lock()) {
        // 抢不到锁
        return;
    }
    // 抢到锁:临界区
}
std::adopt_lock

lock_guard,unique_lock可用

cpp 复制代码
std::lock_guard<std::mutex> g(m, std::adopt_lock);
std::unique_lock<std::mutex> g(m, std::adopt_lock);

等价语义:

  • 构造:不调用 m.lock()
  • 析构:调用 m.unlock()
    使用 adopt_lock 前,当前线程必须已经成功锁住同一个 mutex
cpp 复制代码
std::mutex m;

void f() {
    m.lock(); // 锁住

    std::lock_guard<std::mutex> g(m, std::adopt_lock); // 接管
    // 临界区
} // 自动 unlock

多锁死锁

死锁条件
  1. 互斥:锁一次只能被一个线程持有
  2. 占有且等待:线程持有一把锁,同时等待另一把
  3. 不可抢占:锁只能由持有线程释放
  4. 循环等待 :线程之间形成等待环
    多把锁死锁,核心就是第 4 条:循环等待。

示例:

cpp 复制代码
std::mutex m1, m2;

// 线程 A
void f1() {
    m1.lock();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    m2.lock();
    // ...
    m2.unlock();
    m1.unlock();
}

// 线程 B
void f2() {
    m2.lock();
    std::this_thread::sleep_for(std::chrono::seconds(1));
    m1.lock();
    // ...
    m1.unlock();
    m2.unlock();
}
std::lock
cpp 复制代码
std::lock(m1, m2, ...);

同时锁住所有 mutex。

cpp 复制代码
#include <mutex>

std::mutex m1, m2;

void f1() {
    std::lock(m1, m2); 
    std::lock_guard<std::mutex> g1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> g2(m2, std::adopt_lock);
    // 临界区:同时持有 m1 和 m2
} // 自动释放

void f2() {
	std::unique_lock<std::mutex> lk1(m1, std::defer_lock);
	std::unique_lock<std::mutex> lk2(m2, std::defer_lock);
	std::lock(lk1, lk2); 
	// 临界区
} // 自动释放
std::scoped_lock

C++17

cpp 复制代码
#include <mutex>
using std::scoped_lock;
std::scoped_lock lk(m1, m2, m3);
  • 类似于内部调用 std::lock(m1, m2, m3) + adopt_lock
cpp 复制代码
std::mutex m1, m2;

void f() {
    std::scoped_lock lock(m1, m2); // 一行搞定
    // 临界区
}

tips

其实还有其他几个锁,但是都不常用

  • std::timed_mutex / std::recursive_timed_mutex:支持 try_lock_for/try_lock_until,避免无限期阻塞。
  • std::shared_mutex(C++17)+ std::shared_lock:读多写少场景,读锁共享、写锁独占。
  • std::atomic:对简单计数/标志位,往往比 mutex 更轻
相关推荐
choumin15 分钟前
C++中std::move和std::forward的关系和区别
c++·std move·std forward·关系和区别
云和数据.ChenGuang15 分钟前
python sorted函数详细剖析
开发语言·人工智能·windows·python·1024程序员节
程序猿编码20 分钟前
YOLOv5/v7/YOLOX 全支持:基于 TensorRT 的工业视觉推理底座详解
c++·yolo·机器学习·推理·模型推理
外收内放21 分钟前
FastAPI猜字谜实战(4):让AI主持人开口
开发语言·python·学习·fastapi
云和数据.ChenGuang21 分钟前
Python map() 函数详解:从语法、原理到实战用法
开发语言·人工智能·python·langchain·1024程序员节
秋名RG25 分钟前
(二)存储、CPU 与指令执行——程序为什么快,又为什么慢
java·开发语言
Highcharts.js27 分钟前
Highcharts甘特图开发带导航功能的项目管理图示例
开发语言·前端·javascript·甘特图·highcharts·项目管理图
kyle~28 分钟前
C++ 原子操作、内存模型与无锁并发编程
linux·开发语言·c++·操作系统
BeiGuTing_code31 分钟前
Java泛型与Object
java·开发语言
another heaven32 分钟前
【C/C++ C++ 面向对象架构的五大特点与能力场景:封装、继承、多态、抽象与 RAII(含实战案例)】
c语言·c++·架构