一、基础互斥锁

基础互斥锁是同步的核心,封装了系统原生互斥锁,提供 “排他性所有权”—— 同一时间仅一个线程能持有锁,其他线程尝试加锁会阻塞或直接失败。

核心基础锁:std::mutex

最常用的互斥锁,排他性、非递归(同一线程不能多次加锁)。

核心成员函数
(1)lock()
  • 原型:void lock();
  • 参数:无。
  • 返回值:无。
  • 核心作用:阻塞式加锁。若锁当前未被占用,当前线程会立即持有锁;若锁已被其他线程占用,当前线程会阻塞,直到锁被释放;若当前线程已持有该锁(非递归特性),会直接导致死锁
(2)unlock()
  • 原型:void unlock();
  • 参数:无。
  • 返回值:无。
  • 核心作用:释放锁。仅持有锁的线程能调用,否则行为未定义;释放后,所有阻塞在该锁上的线程会进入竞争状态,胜者获取锁。
(3)try_lock()
  • 原型:bool try_lock();
  • 参数:无。
  • 返回值:布尔类型(true/false)。
    • true:加锁成功(锁未被占用,当前线程持有锁);
    • false:加锁失败(锁已被其他线程占用)。
  • 核心作用:非阻塞式加锁。尝试加锁,成功则进入临界区,失败直接返回,不阻塞当前线程(适用于 “尝试访问,失败则做其他事” 的场景)。

关键规则

  • 锁的生命周期:锁被销毁时,必须未被任何线程持有,否则行为未定义;
  • 传参注意:线程函数中传递 std::mutex 时,必须用 std::ref(mtx)—— 线程构造会将参数拷贝到内部结构体,ref 能保证传递的是原锁的引用(否则每个线程操作独立拷贝,无法同步)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

// 共享资源(临界区)
int g_count = 0;
// 互斥锁:保护临界区
mutex g_mtx;

// 线程函数:累加共享变量(必须用ref传锁和共享变量)
void AddCount(size_t n, int& count, mutex& mtx) {
    mtx.lock(); // 阻塞加锁,独占临界区
    for (size_t i = 0; i < n; ++i) {
        ++count; // 无锁时会因多线程竞争导致结果错误
    }
    mtx.unlock(); // 释放锁,允许其他线程竞争
}

int main() {
    // 创建两个线程,分别累加100万和200万(用ref传引用)
    thread t1(AddCount, 1000000, ref(g_count), ref(g_mtx));
    thread t2(AddCount, 2000000, ref(g_count), ref(g_mtx));

    t1.join();
    t2.join();

    cout << "最终计数(无数据竞争):" << g_count << endl; // 正确输出3000000
    return 0;
}

带超时的互斥锁:std::timed_mutex

继承 std::mutex 的所有特性,额外提供超时加锁接口,解决 “永久阻塞加锁” 的问题(避免线程因锁未释放而一直阻塞)。

新增核心成员函数(继承 lock/unlock/try_lock)
(1)try_lock_for()
  • 原型:template <class Rep, class Period> bool try_lock_for(const chrono::duration<Rep,Period>& rel_time);
  • 参数:rel_time—— 相对超时时间(std::chrono::duration 类型),如 chrono::seconds(1)(1 秒)、chrono::milliseconds(500)(500 毫秒)。
  • 返回值:布尔类型(true/false)。
    • true:超时前成功获取锁;
    • false:超时后仍未获取锁(直接返回,不阻塞)。
  • 核心作用:限时阻塞加锁。尝试加锁,若锁未被占用则立即持有;若已被占用,阻塞 rel_time 时长,超时仍未获取则返回失败。
(2)try_lock_until()
  • 原型:template <class Clock, class Duration> bool try_lock_until(const chrono::time_point<Clock,Duration>& abs_time);
  • 参数:abs_time—— 绝对超时时间点(std::chrono::time_point 类型),如 “2026-01-19 10:00:00”。
  • 返回值:布尔类型(true/false)。
    • true:到达 abs_time 前成功获取锁;
    • false:到达 abs_time 仍未获取锁。
  • 核心作用:限时阻塞加锁(按绝对时间点)。适用于 “必须在某个时间点前完成加锁” 的场景(如定时任务)。
样例:std::timed_mutex 超时加锁
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

timed_mutex g_tmtx;

void TryLockWithTimeout(int thread_id) {
    // 尝试加锁,最多阻塞1秒(相对时间)
    if (g_tmtx.try_lock_for(chrono::seconds(1))) {
        cout << "线程" << thread_id << ":加锁成功,执行临界区操作" << endl;
        this_thread::sleep_for(chrono::seconds(2)); // 模拟临界区执行
        g_tmtx.unlock(); // 释放锁
    } else {
        cout << "线程" << thread_id << ":加锁超时(1秒),放弃执行" << endl;
    }
}

int main() {
    thread t1(TryLockWithTimeout, 1);
    thread t2(TryLockWithTimeout, 2); // t1持有锁2秒,t2会超时

    t1.join();
    t2.join();
    return 0;
}
运行结果
线程1:加锁成功,执行临界区操作
线程2:加锁超时(1秒),放弃执行

递归互斥锁:std::recursive_mutex

继承 std::mutex 的所有特性,唯一区别是支持递归加锁(同一线程可多次加锁,需对应多次解锁),解决 “线程内部嵌套加锁” 的场景。

核心特性
  • 递归所有权:同一线程可多次调用 lock(),每次加锁计数 + 1;解锁时需调用相同次数的 unlock(),计数归 0 才释放锁;
  • 其他线程竞争:若锁被当前线程持有(计数 > 0),其他线程调用 lock() 会阻塞,try_lock() 返回 false
  • 适用场景:函数嵌套调用(如 A 函数加锁后调用 B 函数,B 函数也需要加同一把锁)。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

recursive_mutex g_rmtx;
int g_val = 0;

// 嵌套函数:需要加同一把锁
void NestedFunc() {
    g_rmtx.lock(); // 第二次加锁(递归)
    g_val += 10;
    cout << "嵌套函数:val=" << g_val << endl;
    g_rmtx.unlock(); // 第二次解锁
}

// 主函数:加锁后调用嵌套函数
void MainFunc() {
    g_rmtx.lock(); // 第一次加锁
    g_val += 5;
    cout << "主函数:val=" << g_val << endl;
    NestedFunc(); // 嵌套调用,再次加锁
    g_rmtx.unlock(); // 第一次解锁(计数归0,释放锁)
}

int main() {
    thread t(MainFunc);
    t.join();
    cout << "最终val:" << g_val << endl; // 输出15
    return 0;
}

二、RAII 锁管理类(自动加锁 / 解锁)

RAII(资源获取即初始化)是 C++ 管理资源的核心思想,锁管理类通过 “构造函数加锁、析构函数解锁”,自动管理锁的生命周期,避免因异常 / 忘记解锁导致的死锁。核心包含 std::lock_guard(简单)和 std::unique_lock(灵活)。

简单 RAII 锁:std::lock_guard

功能纯粹,仅支持 “自动加锁 / 解锁”,无额外接口,适用于 “加锁后执行临界区,执行完自动解锁” 的简单场景。

普通构造(自动加锁)

  • 原型:explicit lock_guard(mutex_type& m);
  • 参数:m—— 引用类型的互斥锁(std::mutex/timed_mutex/recursive_mutex 均可)。
  • 核心作用:构造时自动调用 m.lock() 加锁;析构时自动调用 m.unlock() 解锁(无论是否抛出异常)。

接管构造(adopt_lock)

  • 原型:lock_guard(mutex_type& m, adopt_lock_t tag);
  • 参数:m—— 已加锁的互斥锁;tag——std::adopt_lock(标记 “锁已加锁,仅接管管理”)。
  • 核心作用:不自动加锁,仅接管已加锁的锁对象,析构时自动解锁(适用于 “手动加锁后,需要 RAII 保证解锁” 的场景)。

拷贝构造 / 赋值

  • 原型:lock_guard(const lock_guard&) = delete;(拷贝构造禁用);lock_guard& operator=(const lock_guard&) = delete;(拷贝赋值禁用)。
  • 核心原因:避免多个锁管理类管理同一把锁,导致重复解锁。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex g_mtx;
int g_count = 0;

void AddCount(size_t n) {
    // 构造时自动加锁,析构时自动解锁(即使for循环中抛出异常也会解锁)
    lock_guard<mutex> lock(g_mtx); 
    for (size_t i = 0; i < n; ++i) {
        ++g_count;
    }
    // 函数结束,lock析构,自动解锁
}

// 接管已加锁的场景
void AdoptLockDemo() {
    g_mtx.lock(); // 手动加锁
    lock_guard<mutex> lock(g_mtx, adopt_lock); // 接管锁,析构时解锁
    cout << "接管已加锁的锁对象,执行临界区" << endl;
}

int main() {
    thread t1(AddCount, 1000000);
    thread t2(AddCount, 2000000);

    t1.join();
    t2.join();
    cout << "最终计数:" << g_count << endl; // 3000000

    AdoptLockDemo();
    return 0;
}

灵活 RAII 锁:std::unique_lock

功能最丰富的 RAII 锁管理类,支持 “延迟加锁、超时加锁、移动语义、手动加锁 / 解锁”,适用于复杂场景(如条件变量、动态决定是否加锁)。

构造函数

默认构造

  • 原型:unique_lock() noexcept;
  • 核心作用:创建空锁管理对象,不关联任何互斥锁,owns_lock() 返回 false

自动加锁构造

  • 原型:explicit unique_lock(mutex_type& m);
  • 参数:m—— 互斥锁引用。
  • 核心作用:构造时自动调用 m.lock() 加锁,析构时自动解锁。

延迟加锁构造(defer_lock)

  • 原型:unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
  • 参数:m—— 互斥锁引用;tag——std::defer_lock(标记 “延迟加锁”)。
  • 核心作用:仅关联锁对象,不自动加锁,后续需手动调用 lock() 加锁(适用于 “条件满足才加锁” 的场景)。

(4)接管已加锁构造(adopt_lock)

  • 原型:unique_lock(mutex_type& m, adopt_lock_t tag);
  • 参数:m—— 已加锁的互斥锁;tag——std::adopt_lock
  • 核心作用:接管已加锁的锁对象,析构时自动解锁(同 lock_guard 的接管构造)。

(5)超时加锁构造(try_lock_for/try_lock_until)

  • 原型 1(相对时间):template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
  • 原型 2(绝对时间):template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
  • 核心作用:构造时调用 try_lock_for/try_lock_until 加锁,超时则不持有锁;析构时仅在持有锁时解锁。

核心成员函数

(1)lock()/unlock()

  • 原型:void lock(); / void unlock();
  • 核心作用:手动加锁 / 解锁(仅适用于 “延迟加锁” 或 “手动解锁后重新加锁” 的场景)。

(2)try_lock()

  • 原型:bool try_lock();
  • 返回值:true(加锁成功)/false(加锁失败)。
  • 核心作用:手动非阻塞加锁。

(3)owns_lock() / operator bool()

  • 原型:bool owns_lock() const noexcept; / explicit operator bool() const noexcept;
  • 返回值:true(当前持有锁)/false(未持有锁)。
  • 核心作用:判断锁管理对象是否持有锁(如超时加锁后判断是否成功)。

(4)move 构造 / 赋值

  • 原型:unique_lock(unique_lock&& x); / unique_lock& operator=(unique_lock&& rhs) noexcept;
  • 核心作用:支持移动语义,转移锁的管理权(原对象变为 “空对象”,不再管理锁);拷贝构造 / 赋值被禁用。

样例:std::unique_lock 灵活管理锁

#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
using namespace std;

mutex g_mtx;

// 场景1:延迟加锁(条件满足才加锁)
void DeferLockDemo() {
    unique_lock<mutex> lock(g_mtx, defer_lock); // 延迟加锁
    cout << "是否持有锁:" << boolalpha << lock.owns_lock() << endl; // false

    // 模拟条件判断
    bool condition = true;
    if (condition) {
        lock.lock(); // 手动加锁
        cout << "条件满足,加锁成功:" << lock.owns_lock() << endl; // true
        // 临界区操作
    }
    // 析构时自动解锁(若持有锁)
}

// 场景2:超时加锁 + 移动语义
unique_lock<mutex> GetLockWithTimeout() {
    unique_lock<mutex> lock(g_mtx, chrono::seconds(1)); // 超时1秒加锁
    if (lock.owns_lock()) {
        cout << "加锁成功,返回锁管理权" << endl;
        return lock; // 移动构造,转移管理权
    } else {
        cout << "加锁超时,返回空锁" << endl;
        return unique_lock<mutex>(); // 返回空锁
    }
}

int main() {
    DeferLockDemo();

    thread t([]{
        this_thread::sleep_for(chrono::seconds(2)); // 模拟锁被占用
    });
    auto lock = GetLockWithTimeout(); // 接收移动过来的锁
    t.join();

    return 0;
}

三、多锁协同函数(lock/try_lock)

当需要同时锁定多个锁时,直接逐个 lock() 可能导致死锁(如线程 1 锁 A 等 B,线程 2 锁 B 等 A)。C++11 提供 std::lockstd::try_lock 函数,专门解决 “多锁同步” 问题。

1. 多锁原子锁定:std::lock

函数模板,原子化锁定多个锁,避免死锁 —— 要么全部锁定成功,要么全部解锁并阻塞,直到所有锁可用。

原型
template <class Mutex1, class Mutex2, class... Mutexes>
void lock(Mutex1& a, Mutex2& b, Mutexes&... cde);

参数

多个互斥锁的引用(Mutex1/Mutex2 可是 std::mutex/timed_mutex 等任何锁类型)。

返回值

无。

核心作用

  • 原子操作:锁定所有锁,若其中一个锁不可用,会先解锁已锁定的所有锁,再阻塞等待;
  • 死锁避免:内部通过 “锁排序” 算法,确保多个线程按同一顺序锁定多个锁,杜绝死锁。

样例:std::lock 避免多锁死锁

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx_a, mtx_b;

// 线程1:需要同时锁a和b
void TaskA() {
    std::lock(mtx_a, mtx_b); // 原子化锁两个锁
    cout << "TaskA:同时锁定a和b,执行临界区" << endl;
    mtx_a.unlock();
    mtx_b.unlock();
}

// 线程2:同样需要锁a和b(顺序无关,std::lock自动排序)
void TaskB() {
    std::lock(mtx_b, mtx_a); // 即使顺序相反,也不会死锁
    cout << "TaskB:同时锁定b和a,执行临界区" << endl;
    mtx_b.unlock();
    mtx_a.unlock();
}

int main() {
    thread t1(TaskA);
    thread t2(TaskB);

    t1.join();
    t2.join();
    return 0;
}

2. 多锁尝试锁定:std::try_lock

核心定位

函数模板,非阻塞尝试锁定多个锁,要么全部锁定成功,要么解锁已锁定的锁并返回失败锁的下标。

原型
template <class Mutex1, class Mutex2, class... Mutexes>
int try_lock(Mutex1& a, Mutex2& b, Mutexes&... cde);

参数

多个互斥锁的引用。

返回值

int 类型:

  • -1:所有锁都锁定成功;
  • 非负整数:第 n 个锁(下标从 0 开始)锁定失败,已锁定的锁会自动解锁。

核心作用

非阻塞尝试锁定多个锁,失败时不阻塞,可立即处理其他逻辑(如重试、放弃)。

样例:std::try_lock 尝试多锁

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx_a, mtx_b;

void TryLockMulti() {
    int ret = std::try_lock(mtx_a, mtx_b);
    if (ret == -1) {
        // 全部锁定成功
        cout << "TryLockMulti:所有锁锁定成功" << endl;
        mtx_a.unlock();
        mtx_b.unlock();
    } else {
        // 第ret个锁锁定失败
        cout << "TryLockMulti:第" << ret << "个锁锁定失败" << endl;
    }
}

int main() {
    // 先锁定mtx_a,让TryLockMulti失败
    mtx_a.lock();
    thread t(TryLockMulti);
    t.join();
    mtx_a.unlock();

    return 0;
}

运行结果

TryLockMulti:第0个锁锁定失败

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐