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