C++中unique_lock和lock_guard区别小结

 更新时间:2025年04月16日 09:59:02   作者:流星雨爱编程  
本文主要介绍了C++中unique_lock和lock_guard区别,std::unique_lock 和 std::lock_guard属于标准库mute中的互斥锁管理工具,用于简化互斥锁的使用并确保线程安全,具有一定的参考价值,感兴趣的可以了解一下

在 C++ 中,std::unique_lock 和 std::lock_guard 都属于标准库 <mutex> 中的互斥锁管理工具,用于简化互斥锁的使用并确保线程安全。但它们存在一些显著区别,下面为你详细介绍:

1.自动锁定与解锁机制

1) std::lock_guard:一个轻量级的互斥锁包装器,采用了 RAII(资源获取即初始化)技术。当 std::lock_guard 对象被创建时,它会自动锁定所关联的互斥锁;当对象离开其作用域时,会自动解锁该互斥锁。它的设计遵循最小化原则,仅提供最基本的锁管理功能,没有额外的开销。其核心实现原理可以简化为:

template<classMutex>
classlock_guard {
public:
    explicitlock_guard(Mutex& m) : mutex(m) {
        mutex.lock();
    }
    
    ~lock_guard() {
        mutex.unlock();
    }
    
    lock_guard(const lock_guard&) = delete;
    lock_guard& operator=(const lock_guard&) = delete;

private:
    Mutex& mutex;
};

示例代码如下:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void printMessage() {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << "This message is protected by lock_guard." << std::endl;
    // 当 lock_guard 对象离开作用域时,互斥锁会自动解锁
}

int main() {
    std::thread t(printMessage);
    t.join();
    return 0;
}

2) std::unique_lock: 同样基于 RAII 技术,在对象销毁时会自动解锁互斥锁。不过,它的锁定和解锁操作更加灵活,可以在对象创建时选择不立即锁定互斥锁,也可以在对象生命周期内手动锁定和解锁。unique_lock 是 C++11 标准中引入的更高级的锁管理器,设计目标是提供更灵活的锁管理能力。其核心接口包括:

class unique_lock {
public:
    // 构造时可选立即加锁、延迟加锁或尝试加锁
    unique_lock(mutex_type& m, std::defer_lock_t) noexcept;
    unique_lock(mutex_type& m, std::try_to_lock_t);
    unique_lock(mutex_type& m, std::adopt_lock_t);
    
    // 转移构造函数
    unique_lock(unique_lock&& other) noexcept;
    
    // 手动控制接口
    voidlock();
    booltry_lock();
    voidunlock();
    
    // 状态查询
    explicitoperatorbool()constnoexcept;
    boolowns_lock()constnoexcept;
};

示例代码如下:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void printMessage() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 手动锁定互斥锁
    lock.lock();
    std::cout << "This message is protected by unique_lock." << std::endl;
    // 手动解锁互斥锁
    lock.unlock();
}

int main() {
    std::thread t(printMessage);
    t.join();
    return 0;
}

std::unique_lock 允许在对象生命周期内多次手动调用 lock()unlock() 和 try_lock() 方法。这在需要在临界区内进行部分操作后暂时释放锁,执行一些非关键操作,然后再次锁定的场景中很有用。如:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void work() {
    std::unique_lock<std::mutex> lock(mtx);
    std::cout << "Entered critical section." << std::endl;
    // 执行部分临界区操作
    lock.unlock();
    std::cout << "Temporarily released the lock." << std::endl;
    // 执行一些非关键操作
    lock.lock();
    std::cout << "Re - entered critical section." << std::endl;
    // 继续执行临界区操作
}

int main() {
    std::thread t(work);
    t.join();
    return 0;
}

2.灵活性

1)std::lock_guard: 功能相对单一,缺乏灵活性。一旦创建,就会立即锁定互斥锁,并且在其生命周期内无法手动解锁,只能在对象离开作用域时自动解锁。

2)std::unique_lock:具有更高的灵活性。它支持三种锁定策略:

std::defer_lock_t   // 延迟锁定
std::try_to_lock_t  // 尝试锁定
std::adopt_lock_t   // 接管已锁定状态
  • 延迟锁定(std::defer_lock):创建 std::unique_lock 对象时,可以选择不立即锁定互斥锁。这在需要先进行一些准备工作,之后再锁定互斥锁的场景中非常有用。如:
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void work() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
    // 进行一些无需加锁的准备工作
    std::cout << "Doing some preparation work..." << std::endl;
    lock.lock();
    std::cout << "Critical section entered." << std::endl;
    // 临界区代码
    lock.unlock();
    std::cout << "Critical section exited." << std::endl;
}

int main() {
    std::thread t(work);
    t.join();
    return 0;
}
  • 尝试锁定(std::try_to_lock):使用 std::try_to_lock 可以尝试锁定互斥锁,如果互斥锁当前已被其他线程锁定,std::unique_lock 对象不会阻塞,而是立即返回,可通过 owns_lock() 方法判断是否成功锁定。如:
#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void work() {
    std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
    if (lock.owns_lock()) {
        std::cout << "Successfully locked the mutex." << std::endl;
        // 临界区代码
    } else {
        std::cout << "Failed to lock the mutex, doing other work." << std::endl;
        // 执行其他不需要锁定互斥锁的工作
    }
}

int main() {
    std::thread t(work);
    t.join();
    return 0;
}

3.所有权转移

std::lock_guard

  • 不支持所有权转移,即不能将一个 std::lock_guard 对象的互斥锁所有权转移给另一个对象。

std::unique_lock

  • 支持所有权转移,可以通过移动构造函数或移动赋值运算符将互斥锁的所有权从一个 std::unique_lock 对象转移到另一个对象。这在函数返回 std::unique_lock 对象或需要在不同作用域之间传递锁的所有权时非常有用。

示例代码如下:

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

std::unique_lock<std::mutex> getLock() {
    std::unique_lock<std::mutex> lock(mtx);
    return std::move(lock);
}

void printMessage() {
    std::unique_lock<std::mutex> lock = getLock();
    std::cout << "This message is protected by transferred unique_lock." << std::endl;
}

int main() {
    std::thread t(printMessage);
    t.join();
    return 0;
}

4.可与条件变量配合使用

std::unique_lock 能够与 std::condition_variable 一起使用,std::condition_variable 的 wait()wait_for() 和 wait_until() 等方法要求传入 std::unique_lock 对象,因为在等待期间需要释放和重新获取锁。

#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>

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

void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    std::cout << "Worker thread is working." << std::endl;
}

int main() {
    std::thread t(worker);
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
    t.join();
    return 0;
}

5.性能开销

std::lock_guard

  • 由于其功能简单,没有额外的状态管理,因此性能开销相对较小,适合用于简单的锁定场景。

std::unique_lock

  • 为了支持更多的灵活性,std::unique_lock 需要维护更多的状态信息,因此性能开销相对较大。在对性能要求极高且锁定逻辑简单的场景下,使用 std::lock_guard 更为合适。

综上所述,std::lock_guard 适用于简单的锁定场景,追求简洁性和较低的性能开销;而 std::unique_lock 则适用于需要更复杂锁定逻辑、支持所有权转移的场景,但会带来一定的性能开销。

到此这篇关于C++中unique_lock和lock_guard区别小结的文章就介绍到这了,更多相关C++ unique_lock和lock_guard区别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C++堆和栈的区别与联系讲解

    C++堆和栈的区别与联系讲解

    今天小编就为大家分享一篇关于C++堆和栈的区别与联系讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • C++连接数据库SqlServer、MySql、Oracle、Access、SQLite、PostgreSQL、MongoDB、Redis

    C++连接数据库SqlServer、MySql、Oracle、Access、SQLite、PostgreSQL、Mong

    C++是一种通用的编程语言,可以使用不同的库和驱动程序来连接各种数据库,以下是一些示例代码,演示如何使用 C++ 连接 SQL Server、MySQL、Oracle、ACCESS、SQLite 、 PostgreSQL、MongoDB、Redis数据库
    2024-08-08
  • Qt 10进制和16进制转换的使用示例

    Qt 10进制和16进制转换的使用示例

    在编程过程中,处理16进制字符串与10进制数字之间的转换是很常见的需求,本文主要介绍了Qt 10进制和16进制转换的使用示例,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • C++实现LeetCode(90.子集合之二)

    C++实现LeetCode(90.子集合之二)

    这篇文章主要介绍了C++实现LeetCode(90.子集合之二),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C语言进阶练习二叉树的递归遍历

    C语言进阶练习二叉树的递归遍历

    树是一种重要的非线性数据结构,直观地看,它是数据元素(在树中称为结点)按分支关系组织起来的结构,很象自然界中的树那样。树结构在客观世界中广泛存在,如人类社会的族谱和各种社会组织机构都可用树形象表示,本篇介绍二叉树的递归与非递归遍历的方法
    2022-06-06
  • C++中I/O模型之select模型实例

    C++中I/O模型之select模型实例

    这篇文章主要介绍了C++中I/O模型的select模型,实例讲述了I/O模型的用法,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-10-10
  • C++访问者模式模板函数无法重载的问题解决

    C++访问者模式模板函数无法重载的问题解决

    本文主要介绍了C++访问者模式模板函数无法重载的问题解决,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • C++实现std::set的示例项目

    C++实现std::set的示例项目

    std::set是C++标准库中的关联容器,提供有序唯一元素集合,本文就来介绍一下std::set的具体使用,具有一定的参考价值,感兴趣的可以了解一下
    2025-02-02
  • Cocos2d-x 3.x入门教程(一):基础概念

    Cocos2d-x 3.x入门教程(一):基础概念

    这篇文章主要介绍了Cocos2d-x 3.x入门教程(一):基础概念,本文讲解了Director、Scene、Layer、Sprite等内容,需要的朋友可以参考下
    2014-11-11
  • C++ Eigen库计算矩阵特征值及特征向量

    C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06

最新评论