C++中的std::lock_guard和std::unique_lock

本文深入探讨C++中std::lock_guard与std::unique_lock的使用方法及特性。std::lock_guard是最简单的锁管理对象,构造时加锁,析构时解锁,避免了std::mutex的锁忘记问题。std::unique_lock功能更强大,提供了更多接口,如try_lock、try_lock_for等,使线程同步更为灵活。

std::lock_guard

这是最简单的一个管理锁的对象。只有构造和析构函数,在构造的时候加锁,析构的时候解锁,解锁后就不能使用该对象再加锁了。可以避免使用std::mutex时忘记解锁的情况,同时可以方便处理异常。

简单的实例:

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <stdexcept>

std::mutex mtx;

void print_even(int n) {
    if(n % 2 == 0) {
        std::cout << n << " is even\n";
    } else {
        throw std::logic_error("not even\n");
    }
}

void print_thread_id(int id) {
    try {
    	// 在这里,try块是单独一个部分,离开块之后,锁就自动析构了
        std::lock_guard<std::mutex>lck(mtx);
        print_even(id);
    } catch(std::logic_error&) {
        std::cout << "[exception caught]\n";
    }
}

int main() {
    // 创建线程组
    std::vector<std::thread>threads;
    for(int i = 0; i < 10; ++i) {
        threads.emplace_back(std::thread(print_thread_id, i + 1));
    }
    // 所有线程等待归并
    for(auto& th : threads) {
        th.join();
    }
    return 0;
}
/*
输出结果:
[exception caught]
2 is even
[exception caught]
4 is even
[exception caught]
6 is even
[exception caught]
8 is even
[exception caught]
10 is even
*/

std::unique_lock

拥有std::lock_guard所有的功能,但是更加灵活,也有更多的接口;时间和空间要求也更高了。

给出常用的5个构造函数:

unique_lock() noexcept;                                   // (1)
explicit unique_lock (mutex_type& m);                     // (2)
unique_lock (mutex_type& m, try_to_lock_t tag);           // (3)
unique_lock (mutex_type& m, defer_lock_t tag) noexcept;   // (4)
unique_lock (mutex_type& m, adopt_lock_t tag);            // (5)
  1. 仅仅定义一个对象,不管理任何锁对象
  2. 管理锁对象m,同时尝试调用m.lock()进行加锁,如果别的unique_lock已经管理了当前锁m,那么当前线程阻塞。
  3. 管理锁对象m,如果上锁失败,线程不会阻塞
  4. 管理锁对象m,初始化的时候不会锁住m
  5. 管理锁对象m,而且m应当是一个已经被当前线程锁住的Mutex对象

常用的成员函数:

  • try_lock上锁成功返回true,否则返回false
  • try_lock_for,调用管理对象的try_lock_for函数,成功返回true,失败返回false
  • try_lock_until,同try_lock_for
  • unlock,调用管理对象的unlock函数
  • release,返回管理锁的指针,并释放管理权
  • owns_lock,判断是否拥有了锁。
<think>嗯,用户想了解C++std::lock_guardstd::unique_lock是否可以混用。这个问题很有意思,我得仔细分析一下。 首先需要明确这两个RAII包装器的设计目的。std::lock_guardC++11引入的最基础的互斥量管理类,它的作用就是在构造时锁定互斥量,析构时自动解锁。这种设计确保了在作用域结束时互斥量一定会被释放,避免忘记解锁导致的死锁问题[^3]。 而std::unique_lock是在C++11中同时引入的,但功能更强大。它支持延迟锁定(std::defer_lock)、尝试锁定(try_lock)、所有权转移等高级特性[^1][^4]。这使得它在需要更灵活控制锁的场景中特别有用,比如配合条件变量使用。 关于混用的问题,关键要考虑互斥量的锁定状态。如果在一个作用域内同时使用这两个包装器操作同一个互斥量,会出现双重锁定的问题。因为当lock_guard构造时会尝试锁定互斥量,而此时unique_lock可能已经锁定了同一个互斥量。这会导致未定义行为,很可能会抛出std::system_error异常[^1][^4]。 不过有个例外情况:当使用std::adopt_lock参数时。这个参数表示包装器接管已经锁定的互斥量[^1][^4]。比如先用unique_lock锁定互斥量,然后创建带adopt_lock参数的lock_guard来接管所有权。但要注意,这种用法需要严格确保lock_guard的作用域完全包含在unique_lock的作用域内,否则会导致在unique_lock析构后lock_guard仍试图解锁已释放的互斥量。 其实更推荐的做法是统一使用unique_lock。虽然它的性能开销略大于lock_guard,但提供的灵活性在很多场景下值得这点代价[^2][^4]。特别是需要配合条件变量或转移锁所有权时,unique_lock是必须的。 另外要注意作用域管理。无论使用哪种包装器,都要确保它们的作用域不会重叠,除非使用adopt_lock明确接管所有权。错误的嵌套作用域可能导致难以调试的死锁问题[^3][^4]。 最后总结一下:技术上可以混用但需要非常小心,通常建议优先使用unique_lock以获得更好的灵活性。只有在确定不需要高级特性且追求极致性能的简单场景中,才考虑使用lock_guard。</think>在C++中,`std::lock_guard` `std::unique_lock` **可以一起使用,但需严格遵循互斥量的锁定规则**,否则可能导致死锁或未定义行为。以下是关键原则示例: --- ### **核心原则** 1. **避免双重锁定** 同一线程内对**同一个互斥量**连续锁定(未先解锁)是未定义行为。 ```cpp std::mutex m; { std::lock_guard<std::mutex> lock1(m); // 锁定m std::unique_lock<std::mutex> lock2(m); // 错误!尝试再次锁定m } ``` 2. **作用域嵌套需谨慎** - 若需嵌套,必须使用 `std::adopt_lock` 或 `std::defer_lock` 标记明确所有权。 - `std::adopt_lock`:表示**接管已锁定的互斥量**(不尝试重新锁定)。 - `std::defer_lock`:表示**延迟锁定**(构造时不锁定)。 --- ### **安全混用场景** #### 场景1:先锁定后移交所有权 (adopt_lock) ```cpp std::mutex m; { m.lock(); // 手动锁定 std::lock_guard<std::mutex> guard(m, std::adopt_lock); // 接管所有权 // guard析构时自动解锁m { std::unique_lock<std::mutex> ulock(m, std::defer_lock); // 不锁定 ulock.lock(); // 显式锁定(需确保guard已解锁,否则死锁!) // 操作共享数据... } // ulock析构解锁 } // guard析构解锁(但此处guard已解锁?需注意作用域!) ``` > ⚠️ **风险**:若 `ulock` 在 `guard` 的作用域内尝试锁定,会因双重锁定崩溃。需确保作用域分离。 --- #### 场景2:协同锁定多个互斥量 (std::lock) ```cpp std::mutex m1, m2; { std::unique_lock<std::mutex> ulock1(m1, std::defer_lock); // 延迟锁定 std::lock_guard<std::mutex> guard(m2, std::defer_lock); // 错误!lock_guard不支持defer_lock std::lock(ulock1, guard); // 无法编译:lock_guard不能与std::lock协同 } ``` **正确做法**:统一使用 `std::unique_lock` 管理需协同的锁: ```cpp std::mutex m1, m2; { std::unique_lock<std::mutex> ulock1(m1, std::defer_lock); std::unique_lock<std::mutex> ulock2(m2, std::defer_lock); std::lock(ulock1, ulock2); // 原子锁定多个互斥量 // 操作共享数据... } // 自动解锁 ``` > ✅ 通过 `std::lock` 原子锁定避免死锁[^4]。 --- ### **何时避免混用?** 1. **同一互斥量在重叠作用域内** 混用极易导致双重锁定(除非严格使用 `adopt_lock` 且确保作用域不重叠)。 2. **需要转移锁所有权时** `std::unique_lock` 支持移动语义,可转移互斥量所有权;`lock_guard` 不支持。 3. **需要条件变量时** 条件变量必须搭配 `std::unique_lock` 使用(因需灵活解锁)。 --- ### **总结建议** | **场景** | **推荐工具** | |------------------------------------|-----------------------| | 简单作用域,无嵌套 | `std::lock_guard` [^3] | | 需延迟锁定、条件变量或转移所有权 | `std::unique_lock` [^1] | | 同时锁定多个互斥量 | `std::lock` + `std::unique_lock` [^4] | > ✅ **最佳实践**:优先使用 `std::unique_lock`(灵活性高),仅在简单作用域且无需高级特性时用 `std::lock_guard`(更轻量)[^2][^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值