在C++中,锁是一种同步机制,用于保护共享资源在多线程环境下的访问安全,防止因并发访问导致的数据不一致、竞态条件等问题。

以下是对C++中锁相关概念、类型以及使用方法的详细介绍:

1. 互斥锁(Mutex)

互斥锁(std::mutex)是最基础也是最常用的锁类型。它确保在同一时刻,最多只有一个线程能够获得锁并访问受保护的资源。当一个线程持有互斥锁时,其他尝试获取该锁的线程将被阻塞,直到锁被释放。

基本操作

  • 锁住(Locking):使用std::mutex::lock()方法获取锁。如果锁不可用(即已被其他线程持有),调用线程将被阻塞,直到锁变为可用。
  • 解锁(Unlocking):使用std::mutex::unlock()方法释放锁,使得等待的线程有机会获取锁并继续执行。
std::mutex mtx;
int shared_data = 0;

void thread_function() {
    std::lock_guard<std::mutex> lock(mtx); // 自动锁住和解锁
    shared_data++; // 受保护的操作
}

2. 锁的智能指针封装(常用)

为了避免忘记解锁而导致死锁,通常建议使用RAII(Resource Acquisition Is Initialization)机制来管理锁的生命周期。C++标准库为此提供了两种智能指针式的封装:

  • std::lock_guard:在构造时自动获取锁,在析构时自动释放锁。适用于函数或代码块内部,确保锁在作用域结束时正确释放。
  • std::unique_lock:比std::lock_guard更灵活,除了具备自动锁住和解锁的功能外,还支持:
  • 手动控制锁的获取和释放。
  • 非阻塞尝试锁定(try_lock())和定时尝试锁定(try_lock_for()、try_lock_until())。
  • 支持锁的转让(移动构造和赋值)。
std::mutex mtx;
int shared_data = 0;

void thread_function() {
    std::unique_lock<std::mutex> lock(mtx);
    if (some_condition) {
        lock.unlock(); // 手动释放锁
        // ... 其他操作 ...
    }
    shared_data++; // 受保护的操作
}

3. 递归锁(Recursive Mutex)

递归锁(std::recursive_mutex)允许同一线程多次获取同一个锁而不阻塞自己。这种锁适用于需要在已经持有锁的代码内部再次访问相同锁的情况。每次成功获取锁都会增加锁的递归计数,解锁时递归计数减一,直到计数降为零时锁才真正释放给其他线程。

std::recursive_mutex rmtx;
int shared_data = 0;

void recursive_function() {
    std::lock_guard<std::recursive_mutex> lock(rmtx);
    shared_data++; // 第一次获取锁
    recursive_function(); // 再次调用自己,不会阻塞,递归计数增加
} /

4. 条件变量(Condition Variables)

条件变量(std::condition_variable)与互斥锁配合使用,用于线程间的同步。它们允许线程在满足特定条件之前等待,或者通知其他线程某个条件已变为真。

td::mutex mtx;
std::condition_variable cv;
bool data_ready = false;

void producer_thread() {
    std::unique_lock<std::mutex> lock(mtx);
    // ... 生产数据 ...
    data_ready = true;
    cv.notify_one(); // 通知等待的消费者线程
}

void consumer_thread() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return data_ready; }); // 等待数据准备就绪
    // ... 消费数据 ...
}

5. 其他锁机制

  • 读写锁(std::shared_mutex / std::shared_timed_mutex):允许多个读取者同时访问资源,但同一时间只允许一个写入者。提高了读多写少场景下的并发性能。
  • 原子操作(std::atomic):对于简单数据类型的同步访问,可以使用原子操作代替锁,提供轻量级的无锁同步。

## 最佳实践

  • 最小化临界区:尽可能缩小锁保护的代码范围,以减少锁的竞争和持有时间。
  • 避免死锁:合理安排锁的获取顺序,避免循环等待;避免在持有锁时进行可能会阻塞的操作(如IO、其他锁的获取等)。
  • 使用智能指针管理锁:优先使用std::lock_guard或std::unique_lock确保锁的正确释放。
  • 考虑锁的粒度:根据实际情况选择合适的锁类型和粒度,如使用细粒度锁提高并发性,或使用粗粒度锁简化同步逻辑。

C++提供了丰富的锁机制来应对多线程编程中的同步需求,通过合理选择和使用这些工具