C++多线程之unique_lock的使用详解

 更新时间:2025年04月03日 09:25:18   作者:今夜有雨.  
本文主要介绍了C++多线程之unique_lock的使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

一、为什么会有unique_lock?

因为mutex再管理方面有瑕疵,因此出现了一个互斥量封装器lock_guard来智能的管理mutex。但是lock_guard只是简单的管理,功能比较弱,有瑕疵。因此需要搞出来一个功能更强大的东西出来,而这个东西就是unique_lock。
本质上来说lock_guard和unique_lock都是为了更好地使用各种锁而诞生的,但是unique_lock更为灵活,功能更强大,可做的操作比较多。

unique_lock 有些时候也被称为“灵活锁”

二、std::lock_guard可能存在的问题

序号问题描述细节
1锁的粒度问题std::lock_guard的锁作用域与对象的作用域相同,可能导致锁的粒度过大,影响并发性能。
2无法中途解锁一旦std::lock_guard对象构造,锁将被自动获取,并在对象析构时自动释放,无法中途手动解锁
3不支持条件变量std::lock_guard通常与互斥锁一起使用,但不支持与条件变量一起使用(如std::condition_variable)配合使用。
4不支持递归锁虽然可以与std::recursive_mutex一起使用,但std::lock_guard本身不提供对递归锁的特殊支持
5无法指定锁的尝试获取std::lock_guard总是尝试获取锁,不支持非阻塞(尝试)获取锁
6无法与超时机制结合std::guard不提供超时机制,无法设置获取锁的等待时间

三、什么是unique_lock?

unique_lock是一个更灵活的互斥量封装器,它提供了更多的控制选项,比如延迟锁定、尝试锁、递归锁定、定时锁定等。于std::lock_guard相比,std::unique_lock提供了更多的功能,但也需要更多的管理责任。

如何定义使用unique_lock?

unique_lock的构造

std::mutex mtx;
std::unique_lock <std::mutex> lck1(mtx);//自动上锁
//自动锁定
std::unique_lock <std::mutex> lck2(mtx, std::defer_lock);//延迟锁定
std::unique_lock <std::mutex> lck3(mtx, std::adopt_lock);//接受已锁定的mutex,不允许再次锁定
std::unique_lock <std::mutex> lck4(mtx, std::try_to_lock);//尝试锁定

std::defer_lock的具体含义是告诉std::unique_lock在构造时不要自动锁定互斥锁,而是延迟锁定操作
std::unique_lock提供了一些成员函数,用于管理锁定状态
lock()锁定关联的mutex
unlock()解锁关联的mutex
try_lock()尝试锁定mutex,如果锁定成功,返回true,否则返回false
owns_lock()返回一个布尔值,指示unique_lock是否拥有mutex的所有权

unique_lock的第二个参数

std::unique_lock的构造函数可以接受多个参数,其中第二个参数用于指定如何管理锁的行为。常见的第二个参数有以下几种:

  • std::defer_lock
  • std::try_to_lock
  • std::adopt_lock
  • 超时相关参数(如std::chrono的时间段)

接下来我们依次用代码例子来看

  • std::defer_lock
    std::defer_lock表示延迟锁定。即在创建std::unique_lock对象时,不会立即对互斥锁进行锁定操作。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;
void example_defer_lock()
{
    unique_lock<mutex> lock(mtx,std::defer_lock);//不会立即锁定
    lock.lock();//在需要时显式锁定
    for(int i=0;i<5;i++)
    {
        cout<<"Thread : "<<this_thread::get_id()<<" : "<<i<<endl;
    }
    
    cout<<"Locked with defer_lock"<<endl;
    lock.unlock();//显式解锁
}

int main()
{
    thread t1(example_defer_lock);
    t1.join();

    return 0;
}

这段代码主要展示了如何使用 unique_lock 结合 defer_lock 策略进行互斥锁的操作。通过使用
defer_lock,程序可以将锁的锁定操作延迟到需要的时候,这在某些情况下非常有用,例如在进行一些准备工作后再进行锁定,或者根据条件判断是否需要锁定。同时,使用
unique_lock 可以自动管理锁的生命周期,避免忘记解锁而导致的死锁问题,而显式的 lock 和 unlock
操作则提供了更灵活的控制方式,允许程序在锁定和解锁的时机上有更多的选择。在 example_defer_lock 函数中,先创建
unique_lock 但不锁定,之后根据需要进行锁定,在完成一些操作后解锁,保证资源的正确访问和释放,确保线程安全。

  • std::try_to_lock
    std::try_to_lock表示尝试锁定。在创建std::unique_lock对象时,会尝试锁定互斥锁。如果锁定失败,不会阻塞。
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;

void example_try_lock()
{
    unique_lock<mutex> lck(mtx, try_to_lock);//尝试锁定
    if (lck.owns_lock())
    {
        cout <<this_thread::get_id() <<" I have the lock\n" << endl;
    }
    else
    {
        cout <<""<<this_thread::get_id()<< " I don't have the lock\n" << endl;
    }
}
int main()
{
    // 创建两个线程
    thread t1(example_try_lock);//线程一锁定么互斥量
    thread t2(example_try_lock);//线程二尝试锁定么互斥量,但是因为被线程一占用着,所以没有锁定成功
    t1.join();
    t2.join();

    return 0;
}

在这里插入图片描述

这段代码主要展示了如何使用 unique_lock 的 try_to_lock
特性来尝试锁定互斥锁,并且根据锁定结果进行不同的处理。它创建了两个线程 t1 和 t2,这两个线程会尝试锁定同一个互斥锁
mtx。由于互斥锁的特性,只有一个线程能够成功锁定,另一个线程将无法锁定。通过 owns_lock
方法可以判断线程是否成功获取到锁,并输出相应的信息。这种方式可以避免线程阻塞,而是让线程在无法获取锁时继续执行其他任务或采取其他处理方式。同时,unique_lock
会在析构时自动解锁,避免了手动解锁的繁琐操作。
这种机制在多线程编程中非常有用,特别是当线程需要尝试获取资源,但不希望在获取不到时阻塞等待的情况,允许线程在不阻塞的情况下执行其他任务,提高程序的响应性和资源利用率。

  • std::adopt_lock

std::adopt_lock表示接管已经锁定的互斥锁。此参数假设互斥锁已经在其他地方被锁定,std::unique_lock对象会接管这个锁的所有权。使用了std::adopt_lock,这意味着互斥锁已经被锁定。

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

mutex mtx;

void example_adopt_lock()
{
    mtx.lock();//先锁定互斥锁
    unique_lock<mutex> lock(mtx, adopt_lock);//接管已锁定的锁
    cout<<"Locked with adopt_lock"<<endl;
    //mtx.unlock();

}

int main()
{
    thread t1(example_adopt_lock);
    t1.join();

    return 0;
}

总体来说,这段代码的目的是展示如何在已经手动锁定互斥锁的情况下,使用 std::unique_lock 结合 adopt_lock 来管理该互斥锁,从而简化锁的管理并确保在适当的时候自动解锁。

总结

unique_lock的特点

  • 灵活性:unique_lock提供了更多的控制选项,比如延迟锁定、尝试锁定、递归锁定、定时锁定等。这使得开发者可以根据具体的业务逻辑来精确控制锁的加锁和解锁时机。
  • 可移动性:unique_lock是可移动的,可以拷贝、赋值或移动。这使得开发者可以在需要时轻松的将锁的所有权从一个对象转移到另一个对象。
  • 手动控制:unique_lock支持手动解锁,而lock_guard不支持。这提供了更大的灵活性,但也需要开发者自行管理锁的加锁和解锁过程。

到此这篇关于C++多线程之unique_lock的使用详解的文章就介绍到这了,更多相关C++ unique_lock内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! 

相关文章

  • C语言怎么获得进程的PE文件信息

    C语言怎么获得进程的PE文件信息

    这篇文章主要介绍了C语言怎么获得进程的PE文件信息的相关代码,需要的朋友可以参考下
    2016-01-01
  • MFC之ComboBox控件用法实例教程

    MFC之ComboBox控件用法实例教程

    这篇文章主要介绍了MFC之ComboBox控件用法,包括了ComboBox控件常见的各类用法,非常具有实用价值,需要的朋友可以参考下
    2014-09-09
  • C++基础学习之输入输出流详解

    C++基础学习之输入输出流详解

    C++是一种广泛应用的编程语言,其输入和输出是程序所必须的基本操作之一。本文将介绍C++中的输入和输出操作,包括输入输出流、文件输入输出等,希望对读者有所帮助
    2023-04-04
  • c实现linux下的数据库备份

    c实现linux下的数据库备份

    本文给大家简单介绍下c实现linux下的数据库备份的方法和具体的源码,十分的实用,有需要的小伙伴可以参考下。
    2015-07-07
  • C++ Boost log日志库超详细讲解

    C++ Boost log日志库超详细讲解

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • opencv实现图形轮廓检测

    opencv实现图形轮廓检测

    这篇文章主要为大家详细介绍了opencv实现图形轮廓检测,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • 一起来看看C++STL容器之string类

    一起来看看C++STL容器之string类

    这篇文章主要为大家详细介绍了C++STL容器之string类,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • C++深入讲解new与deleted关键字的使用

    C++深入讲解new与deleted关键字的使用

    这篇文章主要介绍了C++中new与deleted关键字的使用,new在动态内存中为对象分配空间并返回一个指向该对象的指针;delete接受一个动态对象的指针, 销毁该对象, 并释放与之关联的内存
    2022-05-05
  • C语言示例讲解do while循环语句的用法

    C语言示例讲解do while循环语句的用法

    在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句。一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件
    2022-06-06
  • 枚举类型的定义和应用总结

    枚举类型的定义和应用总结

    如果一种变量只有几种可能的值,可以定义为枚举类型。所谓“枚举类型”是将变量的值一一列举出来,变量的值只能在列举出来的值的范围内
    2013-10-10

最新评论