C++ 锁类型大全详解

1. 锁的分类概览

C++ 中的锁可以分为几大类,让我用一个清晰的分类图来展示:

text

复制代码
C++ 锁类型
├── 基本互斥锁 (Mutexes)
│   ├── std::mutex
│   ├── std::recursive_mutex
│   ├── std::timed_mutex
│   └── std::recursive_timed_mutex
├── 读写锁 (Read-Write Locks)
│   └── std::shared_mutex (C++17)
├── 锁管理器 (Lock Guards)
│   ├── std::lock_guard
│   ├── std::unique_lock
│   ├── std::shared_lock (C++14)
│   └── std::scoped_lock (C++17)
├── 无锁编程 (Lock-Free)
│   ├── std::atomic
│   └── std::atomic_flag
└── 其他同步原语
    ├── std::condition_variable
    ├── std::once_flag
    └── std::latch / std::barrier (C++20)

2. 基本互斥锁 (Mutexes)

2.1 std::mutex - 标准互斥锁

特点:最基本的互斥锁,不支持递归锁定

cpp

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

std::mutex mtx;
int shared_data = 0;

void basic_mutex_example() {
    // 方法1:手动锁定(不推荐)
    mtx.lock();
    shared_data++;
    mtx.unlock();
    
    // 方法2:使用锁管理器(推荐)
    std::lock_guard<std::mutex> lock(mtx);
    shared_data++;
}

void problem_example() {
    // 错误:同一线程重复锁定会导致死锁
    mtx.lock();
    mtx.lock();  // 这里会死锁!
    mtx.unlock();
    mtx.unlock();
}

2.2 std::recursive_mutex - 递归互斥锁

特点:允许同一线程多次锁定同一个互斥量

cpp

复制代码
#include <mutex>
#include <iostream>

std::recursive_mutex rec_mtx;

void recursive_function(int depth) {
    std::lock_guard<std::recursive_mutex> lock(rec_mtx);
    
    std::cout << "深度: " << depth << std::endl;
    
    if (depth > 0) {
        recursive_function(depth - 1);  // 递归调用,需要递归锁
    }
}

class RecursiveClass {
private:
    std::recursive_mutex mtx_;
    int data_ = 0;
    
public:
    void method1() {
        std::lock_guard<std::recursive_mutex> lock(mtx_);
        data_++;
        method2();  // 调用另一个需要锁的方法
    }
    
    void method2() {
        std::lock_guard<std::recursive_mutex> lock(mtx_);  // 可以再次锁定
        data_ *= 2;
    }
};

2.3 std::timed_mutex - 定时互斥锁

特点:支持带超时的锁定操作

cpp

复制代码
#include <mutex>
#include <thread>
#include <chrono>

std::timed_mutex timed_mtx;

void timed_example() {
    // 尝试立即锁定
    if (timed_mtx.try_lock()) {
        std::cout << "立即锁定成功" << std::endl;
        timed_mtx.unlock();
    }
    
    // 尝试在指定时间内锁定
    if (timed_mtx.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "100ms内锁定成功" << std::endl;
        timed_mtx.unlock();
    } else {
        std::cout << "100ms内锁定失败" << std::endl;
    }
    
    // 尝试在指定时间点前锁定
    auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(50);
    if (timed_mtx.try_lock_until(deadline)) {
        std::cout << "在截止时间前锁定成功" << std::endl;
        timed_mtx.unlock();
    }
}

// 使用 unique_lock 的定时功能
void timed_with_guard() {
    std::unique_lock<std::timed_mutex> lock(timed_mtx, std::defer_lock);
    
    if (lock.try_lock_for(std::chrono::milliseconds(50))) {
        std::cout << "使用锁管理器定时锁定成功" << std::endl;
    }
}

2.4 std::recursive_timed_mutex - 递归定时互斥锁

特点:结合了递归锁和定时锁的功能

cpp

复制代码
#include <mutex>

std::recursive_timed_mutex rec_timed_mtx;

void recursive_timed_example() {
    // 可以递归锁定
    rec_timed_mtx.lock();
    rec_timed_mtx.lock();  // 允许,因为是递归锁
    
    // 也可以定时锁定
    if (rec_timed_mtx.try_lock_for(std::chrono::milliseconds(100))) {
        // 成功锁定
        rec_timed_mtx.unlock();
    }
    
    rec_timed_mtx.unlock();
    rec_timed_mtx.unlock();
}

3. 读写锁 (Read-Write Locks)

3.1 std::shared_mutex - 共享互斥锁 (C++17)

特点:允许多个读操作同时进行,但写操作需要独占访问

cpp

复制代码
#include <shared_mutex>
#include <map>
#include <vector>
#include <thread>

class ThreadSafeDictionary {
private:
    mutable std::shared_mutex rw_mutex_;
    std::map<std::string, std::string> data_;
    
public:
    // 读操作 - 使用共享锁,允许多个线程同时读
    std::string lookup(const std::string& key) const {
        std::shared_lock<std::shared_mutex> lock(rw_mutex_);
        auto it = data_.find(key);
        if (it != data_.end()) {
            return it->second;
        }
        return "";
    }
    
    // 另一个读操作
    bool contains(const std::string& key) const {
        std::shared_lock<std::shared_mutex> lock(rw_mutex_);
        return data_.find(key) != data_.end();
    }
    
    // 写操作 - 使用独占锁,只允许一个线程写
    void insert(const std::string& key, const std::string& value) {
        std::unique_lock<std::shared_mutex> lock(rw_mutex_);
        data_[key] = value;
    }
    
    // 批量读操作
    std::vector<std::string> get_all_keys() const {
        std::shared_lock<std::shared_mutex> lock(rw_mutex_);
        std::vector<std::string> keys;
        for (const auto& pair : data_) {
            keys.push_back(pair.first);
        }
        return keys;
    }
};

// 使用示例
void usage_example() {
    ThreadSafeDictionary dict;
    
    // 多个线程可以同时读取
    std::thread reader1([&]() {
        auto value = dict.lookup("key1");
    });
    
    std::thread reader2([&]() {
        auto keys = dict.get_all_keys();
    });
    
    // 但写操作会互斥
    std::thread writer([&]() {
        dict.insert("key1", "value1");
    });
    
    reader1.join();
    reader2.join();
    writer.join();
}

4. 锁管理器 (Lock Guards)

4.1 std::lock_guard - 基础锁管理器

特点:最简单的 RAII 锁管理器

cpp

复制代码
#include <mutex>

std::mutex mtx;

void lock_guard_examples() {
    // 基本用法
    {
        std::lock_guard<std::mutex> lock(mtx);
        // 临界区代码
    } // 自动解锁
    
    // 保护整个函数
    void critical_function() {
        std::lock_guard<std::mutex> lock(mtx);
        // 整个函数都在锁保护下
    }
    
    // 保护部分代码块
    void partial_protection() {
        // 非临界区代码
        prepare_data();
        
        {
            std::lock_guard<std::mutex> lock(mtx);
            // 临界区代码
            process_shared_data();
        } // 锁在这里释放
        
        // 更多的非临界区代码
        cleanup();
    }
}

4.2 std::unique_lock - 灵活锁管理器

特点:提供完整的锁控制功能

cpp

复制代码
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;

void unique_lock_examples() {
    // 1. 延迟锁定
    std::unique_lock<std::mutex> lock1(mtx, std::defer_lock);
    // ... 准备工作
    lock1.lock();  // 手动锁定
    
    // 2. 尝试锁定
    std::unique_lock<std::mutex> lock2(mtx, std::try_to_lock);
    if (lock2.owns_lock()) {
        // 锁定成功
    }
    
    // 3. 条件变量必须使用 unique_lock
    std::unique_lock<std::mutex> lock3(mtx);
    cv.wait(lock3, []{ return some_condition; });
    
    // 4. 手动解锁和重新锁定
    std::unique_lock<std::mutex> lock4(mtx);
    // 临界区操作
    lock4.unlock();  // 手动解锁
    // 非临界区操作
    lock4.lock();    // 重新锁定
    // 回到临界区
    
    // 5. 移动语义
    std::unique_lock<std::mutex> lock5(mtx);
    // std::unique_lock<std::mutex> lock6 = lock5;  // 错误:不能拷贝
    std::unique_lock<std::mutex> lock6 = std::move(lock5);  // 正确:移动
}

4.3 std::shared_lock - 共享锁管理器 (C++14)

特点:专门用于管理共享锁(读锁)

cpp

复制代码
#include <shared_mutex>

std::shared_mutex rw_mutex;

void shared_lock_examples() {
    // 读操作使用共享锁
    std::shared_lock<std::shared_mutex> read_lock(rw_mutex);
    // 多个线程可以同时持有共享锁
    
    // 支持所有 unique_lock 的功能
    std::shared_lock<std::shared_mutex> deferred_lock(rw_mutex, std::defer_lock);
    if (deferred_lock.try_lock()) {
        // 锁定成功
    }
    
    // 可以手动解锁
    read_lock.unlock();
    // 做一些不需要锁的操作
    read_lock.lock();  // 重新锁定
}

4.4 std::scoped_lock - 多锁管理器 (C++17)

特点:可以同时安全地锁定多个互斥量

cpp

复制代码
#include <mutex>

std::mutex mtx1, mtx2, mtx3;

void scoped_lock_examples() {
    // 锁定单个互斥量(类似 lock_guard)
    {
        std::scoped_lock lock(mtx1);
        // 临界区
    }
    
    // 锁定多个互斥量(避免死锁)
    {
        std::scoped_lock lock(mtx1, mtx2, mtx3);
        // 所有互斥量都被安全锁定
        // 操作多个共享资源
    } // 所有锁自动释放
    
    // 对比:C++17 之前的做法
    {
        std::lock(mtx1, mtx2, mtx3);  // 同时锁定,避免死锁
        std::lock_guard<std::mutex> lock1(mtx1, std::adopt_lock);
        std::lock_guard<std::mutex> lock2(mtx2, std::adopt_lock);
        std::lock_guard<std::mutex> lock3(mtx3, std::adopt_lock);
        // 更繁琐!
    }
}

5. 无锁编程 (Lock-Free)

5.1 std::atomic - 原子变量

特点:无需锁的线程安全操作

cpp

复制代码
#include <atomic>
#include <thread>
#include <vector>

void atomic_examples() {
    // 基本原子类型
    std::atomic<int> atomic_int(0);
    std::atomic<bool> atomic_bool(false);
    std::atomic<long> atomic_long(0L);
    
    // 原子操作
    atomic_int.fetch_add(1, std::memory_order_relaxed);
    atomic_bool.store(true, std::memory_order_release);
    
    // 运算符重载
    atomic_int++;  // 等价于 fetch_add(1)
    atomic_int += 5;
    
    // 比较交换 (Compare-And-Swap)
    int expected = 10;
    while (!atomic_int.compare_exchange_weak(expected, 20)) {
        // 如果 atomic_int != expected,则 expected 被更新为当前值
        // 继续重试
    }
}

// 无锁计数器示例
class LockFreeCounter {
private:
    std::atomic<int> count_{0};
    
public:
    void increment() {
        count_.fetch_add(1, std::memory_order_relaxed);
    }
    
    void decrement() {
        count_.fetch_sub(1, std::memory_order_relaxed);
    }
    
    int get() const {
        return count_.load(std::memory_order_acquire);
    }
    
    // 无锁的获取并重置
    int exchange(int new_value) {
        return count_.exchange(new_value, std::memory_order_acq_rel);
    }
};

5.2 std::atomic_flag - 原子标志

特点:最简单的原子类型,保证无锁

cpp

复制代码
#include <atomic>
#include <thread>

void atomic_flag_examples() {
    // 初始化(必须使用 ATOMIC_FLAG_INIT)
    std::atomic_flag flag = ATOMIC_FLAG_INIT;
    
    // 测试并设置(原子操作)
    bool was_set = flag.test_and_set(std::memory_order_acquire);
    
    // 清除标志
    flag.clear(std::memory_order_release);
    
    // 检查状态(C++20)
    // bool is_set = flag.test(std::memory_order_acquire);  // C++20
}

// 自旋锁实现
class SpinLock {
private:
    std::atomic_flag flag_ = ATOMIC_FLAG_INIT;
    
public:
    void lock() {
        while (flag_.test_and_set(std::memory_order_acquire)) {
            // 忙等待,但可以让出CPU
            std::this_thread::yield();
        }
    }
    
    void unlock() {
        flag_.clear(std::memory_order_release);
    }
};

// 使用自旋锁
SpinLock spin_lock;
void use_spinlock() {
    std::lock_guard<SpinLock> lock(spin_lock);  // 锁管理器也可以管理自旋锁!
    // 临界区
}

6. 其他同步原语

6.1 std::condition_variable - 条件变量

特点:用于线程间的条件同步

cpp

复制代码
#include <condition_variable>
#include <queue>

class ThreadSafeQueue {
private:
    std::mutex mtx_;
    std::condition_variable cv_;
    std::queue<int> queue_;
    bool stopped_ = false;
    
public:
    void push(int value) {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            queue_.push(value);
        }
        cv_.notify_one();  // 通知一个等待的消费者
    }
    
    bool pop(int& value) {
        std::unique_lock<std::mutex> lock(mtx_);
        
        // 等待条件:队列不为空或已停止
        cv_.wait(lock, [this]() {
            return !queue_.empty() || stopped_;
        });
        
        if (queue_.empty() && stopped_) {
            return false;  // 队列已空且已停止
        }
        
        value = queue_.front();
        queue_.pop();
        return true;
    }
    
    void stop() {
        {
            std::lock_guard<std::mutex> lock(mtx_);
            stopped_ = true;
        }
        cv_.notify_all();  // 通知所有等待的线程
    }
};

6.2 std::once_flag - 一次性初始化

特点:确保某个操作只执行一次

cpp

复制代码
#include <mutex>

class Singleton {
private:
    static std::once_flag init_flag_;
    static Singleton* instance_;
    
    Singleton() = default;
    
public:
    static Singleton& get_instance() {
        std::call_once(init_flag_, []() {
            instance_ = new Singleton();
        });
        return *instance_;
    }
};

std::once_flag Singleton::init_flag_;
Singleton* Singleton::instance_ = nullptr;

// 使用示例
void use_singleton() {
    auto& instance1 = Singleton::get_instance();  // 第一次调用会创建实例
    auto& instance2 = Singleton::get_instance();  // 后续调用直接返回实例
    // instance1 和 instance2 是同一个对象
}

6.3 std::latch 和 std::barrier (C++20)

特点:用于线程汇聚点同步

cpp

复制代码
#include <latch>
#include <barrier>
#include <thread>
#include <vector>

// std::latch - 一次性屏障
void latch_example() {
    const int num_threads = 5;
    std::latch latch(num_threads);  // 需要5个线程到达
    
    std::vector<std::thread> threads;
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back([&latch, i]() {
            // 每个线程做一些工作
            std::this_thread::sleep_for(std::chrono::milliseconds(i * 100));
            std::cout << "线程 " << i << " 完成工作" << std::endl;
            
            latch.count_down();  // 减少计数
            latch.wait();        // 等待所有线程完成
            
            std::cout << "所有线程都完成了!" << std::endl;
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
}

// std::barrier - 可重复使用的屏障
void barrier_example() {
    const int num_threads = 3;
    std::barrier barrier(num_threads);  // 需要3个线程到达
    
    std::vector<std::thread> threads;
    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back([&barrier, i]() {
            for (int phase = 0; phase < 3; ++phase) {
                std::cout << "线程 " << i << " 阶段 " << phase << std::endl;
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
                
                barrier.arrive_and_wait();  // 到达并等待其他线程
                
                std::cout << "阶段 " << phase << " 所有线程同步完成" << std::endl;
            }
        });
    }
    
    for (auto& t : threads) {
        t.join();
    }
}

7. 锁的选择指南

7.1 性能对比表

锁类型 适用场景 性能 功能
std::mutex 一般用途 中等 基本
std::recursive_mutex 递归调用 稍慢 递归锁定
std::timed_mutex 需要超时 稍慢 定时操作
std::shared_mutex 读多写少 读快写慢 读写分离
std::atomic 简单操作 最快 无锁
SpinLock 短期锁定 很快 忙等待

7.2 选择策略

cpp

复制代码
// 策略1:简单临界区
void simple_case() {
    std::mutex mtx;
    std::lock_guard<std::mutex> lock(mtx);  // 性能最好
    // 操作...
}

// 策略2:需要条件变量
void condition_case() {
    std::mutex mtx;
    std::condition_variable cv;
    std::unique_lock<std::mutex> lock(mtx);  // 必须用 unique_lock
    cv.wait(lock, []{ return condition; });
}

// 策略3:读多写少
void read_heavy_case() {
    std::shared_mutex rw_mtx;
    
    // 读操作(多个线程可以同时读)
    std::shared_lock<std::shared_mutex> read_lock(rw_mtx);
    
    // 写操作(独占访问)
    std::unique_lock<std::shared_mutex> write_lock(rw_mtx);
}

// 策略4:需要锁定多个资源
void multiple_resources() {
    std::mutex mtx1, mtx2;
    std::scoped_lock lock(mtx1, mtx2);  // 避免死锁
}

// 策略5:高性能简单操作
void high_performance() {
    std::atomic<int> counter(0);
    counter.fetch_add(1, std::memory_order_relaxed);  // 无锁,最快
}

8. 总结

C++ 提供了丰富的锁类型来满足不同的并发需求:

  1. 基本互斥锁:保护共享数据的基本工具

  2. 读写锁:优化读多写少的场景

  3. 锁管理器:基于 RAII 的安全锁管理

  4. 无锁编程:最高性能的原子操作

  5. 同步原语:复杂的线程协调工具

关键建议

  • 优先使用锁管理器,避免手动锁操作

  • 根据场景选择合适的锁类型

  • 保持临界区尽可能小

  • 读写分离场景使用读写锁

  • 简单操作用原子变量替代锁

掌握这些锁类型,你就能应对各种多线程编程场景了!

相关推荐
wuwu_q2 小时前
用通俗易懂方式,详细讲讲 Kotlin Flow 中的 map 操作符
android·开发语言·kotlin
曼巴UE52 小时前
UE5 C++ Slate 画曲线
开发语言·c++·ue5
ue星空2 小时前
UE5C++UKismetMathLibrary源代码
c++·ue5
向葭奔赴♡2 小时前
Spring IOC/DI 与 MVC 从入门到实战
java·开发语言
minji...2 小时前
C++ 面向对象三大特性之一---多态
开发语言·c++
散峰而望2 小时前
基本魔法语言函数(一)(C语言)
c语言·开发语言·编辑器·github
2401_841495642 小时前
【数据结构】基于BF算法的树种病毒检测
java·数据结构·c++·python·算法·字符串·模式匹配
lucky_syq2 小时前
Scala与Spark算子:大数据处理的黄金搭档
开发语言·spark·scala
封奚泽优3 小时前
使用Labelme进行图像标注
开发语言·python·labelme