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_lockstd::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)) {// 忙等待,但可以让出CPUstd::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_lockcv.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++ 提供了丰富的锁类型来满足不同的并发需求:
-
基本互斥锁:保护共享数据的基本工具
-
读写锁:优化读多写少的场景
-
锁管理器:基于 RAII 的安全锁管理
-
无锁编程:最高性能的原子操作
-
同步原语:复杂的线程协调工具
关键建议:
-
优先使用锁管理器,避免手动锁操作
-
根据场景选择合适的锁类型
-
保持临界区尽可能小
-
读写分离场景使用读写锁
-
简单操作用原子变量替代锁
掌握这些锁类型,你就能应对各种多线程编程场景了!
