std::unique_lock与std::lock_guard区别

本文详细介绍了C++中lock_guard和unique_lock两种线程锁的使用方式,包括它们的构造函数、析构函数控制锁的作用范围,以及如何通过构造函数延时加锁和手动加锁解锁。同时对比了两种锁的赋值操作和资源消耗,帮助读者理解它们的特性和应用场景。

使用方式

lock_guard:通过构造函数和析构函数控制锁的作用范围,创造对象的时候加锁,离开作用域的时候解锁;

std::mutex m_mutex;
void print(int cnt)
{
    std::lock_guard<mutex> lock(m_mutex);
    cout << std::this_thread::get_id() << " " << cnt << endl;
}

unique_lock:可以通过构造函数和析构函数控制锁的作用范围,也可以在构造函数中延时加锁,在需要的时候手动加锁和解锁。

std::mutex m_mutex;
void print(int cnt)
{
    //用unique_lock来对锁进行管理.defer_lock是它的构造参数之一.用来推迟加锁.
    unique_lock<mutex> lock(m_mutex, defer_lock);
    /*
        ...这里可以进行一系列线程安全的操作...
    */
    //手动加锁.
    lock.lock();
    cout << std::this_thread::get_id() << " " << cnt << endl;
    lock.unlock();
    //这里用来计算sum的值.然后打印.因为sum是在函数内定义的局部变量.所以下面的代码是线程安全的.没必要用锁对这段代码进行保护.所以在上面用unlock解锁.
    int sum = 0;
    for (int i = 0; i < cnt; ++i)
    {
        sum += i;
    }
    //最后在手动加锁.不用手动释放.因为会在生命周期结束时自动释放.
    lock.lock();
    cout << std::this_thread::get_id() << " " << sum << endl;
}

赋值操作

lock_guard不支持赋值操作。unique_lock支持可移动赋值,若占有互斥量则解锁互斥,并取得另一者的所有权

资源消耗

unique_lock相对而言更加灵活,对于资源的消耗明显要大一些,因为它要维持mutex的状态;
lock_guard虽然笨重一些,但是资源消耗相对要小一点。

<think>嗯,用户想了解C++中std::lock_guardstd::unique_lock是否可以混用。这个问题很有意思,我得仔细分析一下。 首先需要明确这两个RAII包装器的设计目的。std::lock_guard是C++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]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值