std::unique_lockstd::lock_guard 都是 C++ 中的互斥锁包装器,但它们有不同的特性和使用场景。

1. 基本区别

特性 std::lock_guard std::unique_lock
灵活性 简单,功能有限 灵活,功能丰富
锁管理 RAII,自动锁/解锁 手动控制锁/解锁
性能 更轻量,开销小 稍重,有额外开销
条件变量 不能配合使用 必须配合使用
移动语义 不支持 支持
锁策略 有限支持 丰富支持

2. 代码示例对比

std::lock_guard - 简单自动管理

#include <mutex>

std::mutex mutex;

void simple_function() {
    {
        std::lock_guard<std::mutex> lock(mutex);  // 构造时自动加锁
        // 临界区代码
        // ...
    } // 析构时自动解锁
    // 锁已自动释放
}

std::unique_lock - 灵活手动控制

#include <mutex>

std::mutex mutex;

void flexible_function() {
    std::unique_lock<std::mutex> lock(mutex);  // 构造时自动加锁
    
    // 可以手动解锁
    lock.unlock();
    
    // 执行一些非临界区代码
    // ...
    
    // 可以重新加锁
    lock.lock();
    
    // 更多临界区代码
    // ...
    
} // 如果锁仍被持有,析构时自动解锁

3. 条件变量必须使用 std::unique_lock

#include <mutex>
#include <condition_variable>

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

void consumer() {
    std::unique_lock<std::mutex> lock(mutex);  // 必须用 unique_lock
    
    // wait() 会临时解锁互斥量,并在唤醒时重新加锁
    cv.wait(lock, []{ return data_ready; });
    
    // 处理数据...
}

void producer() {
    {
        std::lock_guard<std::mutex> lock(mutex);  // 这里可以用 lock_guard
        data_ready = true;
    }
    cv.notify_one();
}

4. 延迟锁定的例子

#include <mutex>

std::mutex mutex;

void deferred_locking() {
    // 创建但不立即加锁
    std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
    
    // 执行一些不需要锁的操作
    // ...
    
    // 在需要的时候手动加锁
    lock.lock();
    
    // 临界区代码
    // ...
    
    // 可以手动解锁
    lock.unlock();
    
    // 更多非临界区代码
    // ...
    
    // 如果需要,可以重新加锁
    if (some_condition) {
        lock.lock();
        // ...
    }
} // 自动管理最终状态

5. 尝试加锁的场景

#include <mutex>
#include <chrono>

std::mutex mutex;

void try_lock_examples() {
    // 尝试立即加锁
    std::unique_lock<std::mutex> lock(mutex, std::try_to_lock);
    if (lock.owns_lock()) {
        // 成功获取锁
        // ...
    } else {
        // 锁被其他线程持有,执行替代方案
        // ...
    }
}

void timed_lock_example() {
    std::unique_lock<std::mutex> lock(mutex, std::defer_lock);
    
    // 尝试在100ms内获取锁
    if (lock.try_lock_for(std::chrono::milliseconds(100))) {
        // 成功获取锁
        // ...
    } else {
        // 超时,执行替代方案
        // ...
    }
}

6. 移动语义支持

#include <mutex>
#include <vector>

std::mutex global_mutex;

std::unique_lock<std::mutex> get_lock() {
    std::unique_lock<std::mutex> lock(global_mutex);
    // 一些初始化...
    return lock;  // 可以返回,支持移动语义
}

void move_semantics_example() {
    // 从函数接收锁
    std::unique_lock<std::mutex> lock = get_lock();
    
    // 锁的所有权可以转移
    std::vector<std::unique_lock<std::mutex>> lock_pool;
    lock_pool.push_back(std::move(lock));
    
    // 现在 lock 不再拥有锁的所有权
}

7. 实际应用场景对比

适合 std::lock_guard 的场景:

void simple_critical_section() {
    std::lock_guard<std::mutex> lock(mutex);  // 简单直接
    // 执行一些操作
    // 不需要条件变量
    // 不需要手动控制锁
}

适合 std::unique_lock 的场景:

void complex_critical_section() {
    std::unique_lock<std::mutex> lock(mutex);
    
    // 阶段1:需要锁的操作
    // ...
    
    // 临时释放锁进行IO操作
    lock.unlock();
    perform_expensive_io();
    lock.lock();
    
    // 阶段2:又需要锁的操作
    // ...
    
    // 使用条件变量等待
    cv.wait(lock, []{ return condition; });
    
    // 阶段3:最终操作
    // ...
}

8. 性能考虑

#include <mutex>

class PerformanceSensitiveClass {
private:
    std::mutex mutex_;
    int data_;
    
public:
    // 在简单场景中使用 lock_guard(性能更好)
    void simple_increment() {
        std::lock_guard<std::mutex> lock(mutex_);
        ++data_;
    }
    
    // 在复杂场景中使用 unique_lock(功能更强)
    void complex_operation() {
        std::unique_lock<std::mutex> lock(mutex_);
        
        if (data_ > 100) {
            lock.unlock();
            process_large_data();
            lock.lock();
        }
        
        // 更多操作...
    }
};

9. 锁策略示例

void lock_strategies() {
    std::mutex mutex;
    
    // 1. 默认:立即加锁
    std::unique_lock<std::mutex> lock1(mutex);
    
    // 2. 延迟加锁
    std::unique_lock<std::mutex> lock2(mutex, std::defer_lock);
    
    // 3. 尝试加锁
    std::unique_lock<std::mutex> lock3(mutex, std::try_to_lock);
    
    // 4. 接受已拥有的锁(adopt_lock)
    mutex.lock();
    std::unique_lock<std::mutex> lock4(mutex, std::adopt_lock);
}

总结

使用 std::lock_guard 当:

  • 简单的 RAII 锁管理
  • 整个作用域都需要锁
  • 不需要条件变量
  • 性能是关键考虑

使用 std::unique_lock 当:

  • 需要配合条件变量
  • 需要手动控制锁的时机
  • 需要尝试加锁或超时加锁
  • 需要转移锁的所有权
  • 需要更复杂的锁策略

在大多数简单情况下,std::lock_guard 就足够了,而且性能更好。只有在需要更高级功能时才使用 std::unique_lock

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐