几种锁的对比

好的,那我来详细对比一下 std::lock_guardstd::unique_lock 这两种常用的锁管理方式,以及它们与直接使用锁的区别。

1. std::lock_guard

  • 定义std::lock_guard 是一个简单的锁管理器,用于管理互斥锁的锁定和解锁。它在构造时锁定互斥锁,在析构时自动释放锁。

  • 特点

    • 自动管理 :构造函数锁定锁,析构函数释放锁,确保锁的管理不会被遗忘。
    • 不可移动、不可复制std::lock_guard 对象不能被移动或复制,这意味着锁不能在不同的作用域之间传递。
    • 简单易用 :适用于简单的锁需求场景,不需要复杂的锁管理。
    • 作用域绑定 :锁的生命周期与 std::lock_guard 对象的作用域绑定,一旦超出作用域,锁自动释放。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #include <iostream>
    #include <mutex>
    #include <thread>

    std::mutex mtx;

    void print_block(int n) {
    std::lock_guard<std::mutex> guard(mtx);
    for (int i = 0; i < 5; ++i) {
    std::cout << "Thread " << n << ": " << i << '\n';
    }
    }

    int main() {
    std::thread a(print_block, 1);
    std::thread b(print_block, 2);
    a.join();
    b.join();
    return 0;
    }

2. std::unique_lock

  • 定义std::unique_lock 是一个更灵活的锁管理器,提供了比 std::lock_guard 更多的功能。它也用于管理互斥锁的锁定和解锁。

  • 特点

    • 延迟锁定 :可以在构造时不锁定锁,之后通过调用 lock() 方法显式锁定。
    • 条件变量支持 :与条件变量配合使用时,std::unique_lock 可以在等待条件变量时释放锁,并在条件满足时重新获取锁。
    • 可移动 :支持移动操作,允许锁在不同的作用域之间传递。
    • 作用域绑定 :与 std::lock_guard 类似,锁的生命周期与 std::unique_lock 对象的作用域绑定,超出作用域时自动释放锁。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <iostream>
    #include <mutex>
    #include <thread>
    #include <condition_variable>

    std::mutex mtx;
    std::condition_variable cv;
    bool ready = false;

    void print_id(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, [] { return ready; });
    std::cout << "Thread " << id << '\n';
    }

    int main() {
    std::thread t1(print_id, 1);
    std::thread t2(print_id, 2);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
    std::lock_guard<std::mutex> guard(mtx);
    ready = true;
    }
    cv.notify_all();
    t1.join();
    t2.join();
    return 0;
    }

3. 直接使用锁

  • 定义 :直接使用互斥锁(如 std::mutex)进行锁定和解锁。

  • 特点

    • 手动管理 :需要手动调用 lock()unlock() 方法,容易忘记释放锁,导致死锁。
    • 灵活性高 :提供完整的控制权,可以实现复杂的锁逻辑。
    • 易出错 :手动管理容易出错,如忘记解锁、重复解锁等。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <iostream>
    #include <mutex>
    #include <thread>

    std::mutex mtx;

    void print_block(int n) {
    mtx.lock();
    for (int i = 0; i < 5; ++i) {
    std::cout << "Thread " << n << ": " << i << '\n';
    }
    mtx.unlock();
    }

    int main() {
    std::thread a(print_block, 1);
    std::thread b(print_block, 2);
    a.join();
    b.join();
    return 0;
    }

对比总结

特性 std::lock_guard std::unique_lock 直接使用锁
锁定时机 构造时立即锁定 可延迟锁定 手动锁定
解锁时机 析构时自动解锁 析构时自动解锁 手动解锁
可移动
条件变量支持
适用场景 简单的锁需求,不需要延迟锁定或条件变量支持 复杂的锁需求,需要延迟锁定或条件变量支持 需要完全控制锁的锁定和解锁时机
安全性 高,自动管理锁的生命周期 高,自动管理锁的生命周期 低,容易忘记解锁或重复解锁
灵活性 低,只能构造时锁定,析构时解锁 高,支持延迟锁定、条件变量等待等 高,完全手动控制

总结

  • std::lock_guard :适用于简单的锁需求场景,自动管理锁的生命周期,确保锁的正确释放。
  • std::unique_lock :适用于复杂的锁需求场景,如延迟锁定、条件变量等待等,提供更高的灵活性。
  • 直接使用锁 :适用于需要完全控制锁的锁定和解锁时机的场景,但容易出错,需要谨慎使用。

在实际开发中,推荐优先使用 std::lock_guardstd::unique_lock 来管理锁,以提高代码的安全性和可维护性。