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_guardunique_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 更轻
相关推荐
jiaguangqingpanda2 小时前
Day37-20260205
java·开发语言
历程里程碑2 小时前
21:重谈重定义理解一切皆“文件“及缓存区
linux·c语言·开发语言·数据结构·c++·算法·缓存
wxin_VXbishe2 小时前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·spring boot·python·spring·django·php
weixin_433179332 小时前
Python - 软件对象
开发语言·python
Want5952 小时前
Python新春烟花代码
开发语言·python·pygame
oldmao_20002 小时前
第五章 C++内存模型与原子操作
c++
CSDN_RTKLIB2 小时前
CMake制作动态库与静态库对比
c++
wWYy.2 小时前
C++—集群聊天室(3)CMake详解
开发语言·c++
lsx2024062 小时前
SciPy 稀疏矩阵
开发语言