C++ 互斥锁原理以及实际使用介绍

2023-05-16

        兄弟姐妹们,我又回来了,今天带来实际开发中都需要使用的互斥锁的内容,主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁。实现互斥,可以有以下几种方式:互斥量(Mutex)、递归互斥量(Recursive Mutex)、读写锁(Read-Write Lock)、条件变量(Condition Variable)。

目录

一、互斥原理(mutex)

二、递归互斥量(Recursive Mutex)

三、读写锁(Read-Write Lock)

四、条件变量(Condition Variable)

五、总结

一、互斥原理(mutex)

        互斥锁可以确保在任何时候只有一个线程能够进入临界区。当线程需要进入临界区时,它会尝试获取互斥锁的所有权,如果互斥锁已经被其他线程占用,那么当前线程就会进入阻塞状态,直到互斥锁被释放为止。简单说就是一块区域只能被一个线程执行。

        当一个线程获取到互斥锁的所有权后,它就可以进入临界区进行操作,当操作完成后,它需要释放互斥锁,让其他线程有机会进入临界区。

        下面是一个简单的互斥锁的示例代码,它演示了如何使用 std::mutex 类来保护临界区:


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

// 定义互斥锁
std::mutex g_mutex;

// 临界区代码
void critical_section(int thread_id) {
    // 加锁
    g_mutex.lock();
    // 访问共享资源
    std::cout << "Thread " << thread_id << " enter critical section." << std::endl;
    // 释放锁
    std::this_thread::sleep_for(std::chrono::seconds(5));
    g_mutex.unlock();
}

int main() {
    // 创建两个线程
    std::thread t1(critical_section, 1);
    std::thread t2(critical_section, 2);
    // 等待两个线程执行完成
    t1.join();
    t2.join();
    return 0;
}

        main中创建两个线程去访问资源,但是其中一个需要等待另一个线程5s释放后才能访问,形成对资源的锁定。

        上面的例子使用的是std::mutex实现互斥锁,需要注意这个互斥锁的声明需要相对的全局变量,也就是说对于使用锁的部分它必须是“全局的”。

二、递归互斥量(Recursive Mutex)

        C++ 中的递归互斥量(Recursive Mutex)是一种特殊的互斥量,它可以被同一个线程多次锁定,而不会发生死锁。递归互斥量的实现原理是,在锁定时维护一个锁定计数器,每次解锁时将计数器减一,只有当计数器为 0 时才会释放锁。

        以下是递归互斥量的示例代码:

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

std::recursive_mutex mtx;

void foo(int n) {
    mtx.lock();
    std::cout << "Thread " << n << " locked the mutex." << std::endl;
    if (n > 1) {
        foo(n - 1);
    }
    std::cout << "Thread " << n << " unlocked the mutex." << std::endl;
    mtx.unlock();
}

int main() {
    std::thread t1(foo, 3);
    std::thread t2(foo, 2);
    t1.join();
    t2.join();
    return 0;
}

        在上面的代码中,我们定义了一个递归函数 foo(),它接受一个整数参数 n,表示当前线程的编号。在函数中,我们首先使用递归互斥量 mtx 锁定当前线程,然后输出一条带有线程编号的信息,接着判断如果 n 大于 1,则递归调用 foo() 函数,并将参数减一。最后,我们输出一条解锁信息,并将递归互斥量解锁。

        在主函数中,我们创建了两个线程 t1 和 t2,分别调用 foo() 函数,并传入不同的参数值。由于递归互斥量可以被同一个线程多次锁定,因此在 t1 线程中对 mtx 进行了两次锁定,而在 t2 线程中只进行了一次锁定。

        运行结果:

        可以看到,递归互斥量可以被同一个线程多次锁定,并且在解锁时必须对应减少锁定计数器。这种机制可以避免死锁的发生,但也需要注意使用时的线程安全问题。

三、读写锁(Read-Write Lock)

        读写锁(Read-Write Lock)是一种特殊的互斥锁,用于在多线程环境下对共享资源进行读写操作。它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁的使用可以提高并发性能,特别是当读操作比写操作频繁时。

        在 C++ 中,读写锁可以通过 std::shared_mutex 类型来实现。下面是一个简单的示例代码,演示了如何使用读写锁来保护一个共享的整型变量:

#include <iostream>
#include <thread>
#include <chrono>
#include <shared_mutex>

std::shared_mutex rw_lock; // 读写锁
int shared_var = 0; // 共享变量

// 写线程函数
void writer() {
    for (int i = 0; i < 10; ++i) {
        // 独占写锁
        std::unique_lock<std::shared_mutex> lock(rw_lock);

        // 写共享变量
        ++shared_var;
        std::cout << "Writer thread: write shared_var=" << shared_var << std::endl;

        // 等待一段时间
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

// 读线程函数
void reader(int id) {
    for (int i = 0; i < 10; ++i) {
        // 共享读锁
        std::shared_lock<std::shared_mutex> lock(rw_lock);

        // 读共享变量
        int value = shared_var;
        std::cout << "Reader thread " << id << ": read shared_var=" << value << std::endl;

        // 等待一段时间
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

int main() {
    std::thread t1(writer);
    std::thread t2(reader, 1);
    std::thread t3(reader, 2);
    std::thread t4(reader, 3);
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    return 0;
}

        在上面的代码中,我们定义了一个共享变量 shared_var 和一个读写锁 rw_lock。写线程函数 writer() 独占写锁,对 shared_var 进行自增操作,并输出当前的值。读线程函数 reader() 共享读锁,读取 shared_var 的值,并输出当前的值。所有的线程都会等待一段时间,以模拟实际的操作。

        在主函数中,我们创建了一个写线程和三个读线程。由于读写锁的特性,读线程可以并发读取共享变量,而写线程会独占写锁,只有在写操作完成之后,读线程才能再次读取共享变量。因此,输出结果中读线程的顺序可能会有所不同,但是写线程的操作一定是顺序执行的。

        注意,这里使用 std::unique_lockstd::shared_mutex 类型的对象来获取独占写锁,使用 std::shared_lockstd::shared_mutex 类型的对象来获取共享读锁。这些锁对象会在作用域结束时自动解锁,避免了手动解锁的问题。

四、条件变量(Condition Variable)

        条件变量(Condition Variable)是一种线程间同步机制,用于在某些特定条件下阻塞或唤醒线程。在 C++ 中,条件变量是通过 std::condition_variable 类来实现的。

        下面是一个使用条件变量的示例代码,其中有两个线程,一个线程不停地生产数据,另一个线程则等待数据,当有数据可用时,将数据进行消费。

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

std::queue<int> data_queue; // 数据队列
std::mutex data_mutex; // 互斥锁
std::condition_variable data_cond; // 条件变量

// 生产数据函数
void producer() {
    for (int i = 1; i <= 10; ++i) {
        // 生产数据
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::unique_lock<std::mutex> lock(data_mutex);
        data_queue.push(i);
        std::cout << "Producer thread: produce data " << i << std::endl;

        // 唤醒消费线程
        data_cond.notify_one();
    }
}

// 消费数据函数
void consumer() {
    while (true) {
        // 等待数据
        std::unique_lock<std::mutex> lock(data_mutex);
        data_cond.wait(lock, [] { return !data_queue.empty(); });

        // 消费数据
        int data = data_queue.front();
        data_queue.pop();
        std::cout << "Consumer thread: consume data " << data << std::endl;

        // 检查是否结束
        if (data == 10) {
            break;
        }
    }
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

        在上面的代码中,我们定义了一个数据队列 data_queue 和一个互斥锁 data_mutex,同时定义了一个条件变量 data_cond。生产数据的函数 producer() 不停地往队列中添加数据,每次添加完数据之后,通过调用 data_cond.notify_one() 唤醒等待的消费线程。消费数据的函数 consumer() 通过调用 data_cond.wait(lock, [] { return !data_queue.empty(); }) 来等待数据,当队列中有数据时,将数据从队列中取出并消费,如果取出的数据是最后一个,则退出循环。

        在主函数中,我们创建了一个生产线程和一个消费线程。生产线程生产 10 个数据,消费线程从队列中消费数据,直到消费到最后一个数据为止。

        注意,这里使用了 std::unique_lockstd::mutex 类型的对象来获取互斥锁,并使用 lambda 表达式 [] { return !data_queue.empty(); } 来判断条件是否满足。在调用 wait() 函数时,当前线程会阻塞,直到条件变量被其他线程唤醒或超时。当 wait() 函数返回时,当前线程会重新获取互斥。

简单一些的例子:

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

bool ready = false; // 条件变量
std::mutex data_mutex; // 互斥锁
std::condition_variable data_cond; // 条件变量

void do_something() {
    // 模拟工作
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

void waiting_thread() {
    // 等待条件变量
    std::unique_lock<std::mutex> lock(data_mutex);
    data_cond.wait(lock, [] { return ready; });

    // 条件满足后输出一句话
    std::cout << "Condition satisfied, waiting thread resumes." << std::endl;
    do_something();
}

int main() {
    std::thread t1(waiting_thread);

    // 模拟条件满足后的操作
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    {
        std::lock_guard<std::mutex> lock(data_mutex);
        ready = true;
        data_cond.notify_one();
    }

    t1.join();
    return 0;
}

        在上面的代码中,我们定义了一个条件变量 ready 和一个互斥锁 data_mutex,同时定义了一个条件变量 data_cond。等待条件变量的函数 waiting_thread() 首先获取互斥锁,然后通过调用 data_cond.wait(lock, [] { return ready; }) 等待条件变量,当 ready 为 true 时,线程会被唤醒,输出一句话,并模拟一些工作的操作。在主函数中,我们创建了一个等待条件变量的线程 t1,然后模拟条件满足后的操作,即将 ready 设置为 true,然后通过调用 data_cond.notify_one() 唤醒等待的线程。 

五、总结

        互斥锁保证了计算机资源访问的安全,互斥锁的不当使用同时也加大了程序阻塞的风险。

        提前祝大家五一前工作生活学习一切顺利。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 互斥锁原理以及实际使用介绍 的相关文章

  • 4g图传数传实测

    又一位客户极高的评价 xff0c 为了反馈大家 xff0c 从文章看到的并且加我qq1767893964 xff0c 备注gslink购买者 xff0c 毕淘宝搜索下单的有很大的优惠哦 下面是客户的测试 xff1a 最近南京天气开始变暖 x
  • Unable to locate package错误解决办法

    Ubuntu下执行apt install python pip得到如下错误提示 xff1a Reading package lists Done Building dependency tree Reading state informat
  • 树莓派编译卡死用交换空间问题的解决

    今天在树莓派编译一个ros包时发现 xff0c 每次编译到后速度会特别慢并且卡死 xff0c 经过各种尝试 xff0c 增加2G交换空间后才解决问题 xff0c 编译只花了不到10s xff0c 之前折腾了大半天啊 树莓派3B默认的swap
  • SpringAOP中@EnableAspectJAutoProxy注解的作用

    文章目录 前言从注解开始Import注解封装对象 注入容器 AnnotationAwareAspectJAutoProxyCreator的作用类图回顾IOC对象初始化后置处理器 总结 前言 如果要使用SpringAOP的功能 xff0c 必
  • HDMI转CSI转换板给你做出来了

    小编前段时间一直在做无人机图像的项目 因为项目要求飞机飞行速度较快 小编就像找一款是全局快门 防抖 自动对焦 焦距定焦镜头的一款相机和镜头 首先想到的就是gopro 体积不大 价格也还行 小编之前出去玩一直用对成像效果也是很满意 于是小编就
  • cmakelist.txt 编写教程

    1 CMake编译原理 CMake是一种跨平台编译工具 xff0c 比make更为高级 xff0c 使用起来要方便得多 CMake主要是编写CMakeLists txt文件 xff0c 然后用cmake命令将CMakeLists txt文件
  • VS Code在线安装ESP-IDF出现乱码(已解决)

    VS Code在线安装ESP IDF出现乱码 xff08 已解决 xff09 VS Code安装ESP IDF插件的安装乱码解决办法 VS Code安装 按照乐鑫给出的要求是安装VS Code之前需要安装Git和Python3 xff08
  • ffmpeg推流rtmp指定udp传输

    RTMP Real Time Messaging Protocol 是一个用于音频 视频和数据的传输协议 RTMP 协议本身可以支持 TCP 或 UDP 作为其底层传输协议 在 RTMP 中 xff0c TCP 是默认的传输协议 xff0c
  • 单片机小白学习之路(十五)---定时器和计数器的理解(一)

    目标 xff1a 定时器和计数器的理解 一 1 定时器 计数器简介 定时器 计数器 xff08 Timer Counter xff0c 简称T C xff09 是单片机中最基本的接口之一 即可以定时又可以计数 常用于计数 延时 测量周期 脉
  • stm32---ADXL345

    ADXL345是一款三轴加速度传感器 xff0c 广泛用于手机 游戏手柄等设计 ADXL 支持标准的 I2C 或 SPI 数字接口 xff0c 自带 32 级 FIFO 存储 xff0c 并且内 部有多种运动状态检测和灵活的中断方式等特性
  • fastjson中JSONObject.parse方法使用注意

    今天遇到有同事在使用fastjson的JSONObject时 xff0c 直接在parse方法中传入了一个非json格式的字符串 xff0c 造成有时候报错 xff0c 有时候又能正常返回 问题现象 当你传入一个数值类型时 xff0c 可以
  • HZ和秒之间换算

    Hz和毫秒不能直接换算 xff0c 两者是交流电频率与周期的关系 xff0c 并且是倒数关系 xff1a 周期T 61 1 100 61 0 01秒 61 10毫秒 100Hz即100次 秒 xff0c 即60x100 60秒 xff0c
  • 野火 FireConfig 从SD卡下载镜像到EMMC

    1 用balenaEtcher把镜像下载到SD卡 2 拨码到SD卡启动 3 用MobaXterm当串口终端 xff0c 选择115200 xff0c 取消硬件流 4 输入用户名cat 密码fish 5 输入sudo fire config
  • VCC、VDD、VSS以及VBAT的区别

    原链接 xff1a https blog csdn net LemonLeeB article details 99417945 在STM32 的学习中 xff0c 发现有几种看起来相关的名称 xff0c 分别是VCC VDD VSS VB
  • LWIP_MDNS

    一 xff0e mdns1 什么是mdns xff1f mDNS协议适用于局域网内没有DNS服务器时的域名解析 xff0c 设备通过组播的方式交互DNS记录来完成域名解析 xff0c 约定的组播地址是 xff1a 224 0 0 251 x
  • 组播IGMP

    一 xff0e 什么是组播 xff1f 1 一个发送 组播源 xff0c 多个接收 xff0c 接收的有个特点就是在同一个组播组里面 xff0c 组播组有自己的IP 2 对于组播源来说 xff0c 发送命令到组播IP等于把命令发送到所有组成
  • 单片机小白学习之路(四十三)---LCD12864液晶显示

    目标 xff1a LCD12864原理的理解 1 LCD12864简介 LCD12864可以用来显示字符 数字 汉字 图形等内容 xff0c 其分辨率是128 64点 意思是横着有128个点 xff0c 竖直方向有64点 LCD12864
  • stm32---红外接受

    一个脉冲对应 560us 的连续载波 xff0c 一个逻辑 1 传输需要 2 25ms xff08 560us 脉冲 43 1680us 低电平 xff09 xff0c 一个逻辑 0 的传输需要 1 125ms xff08 560us 脉冲
  • printf重定向

    C语言中printf默认输出设备是显示器 xff0c 当开发板没有时我们就用串口来打印数据 int fputc int ch FILE p USART SendData USART1 ch 如果用串口2打印 xff0c 和换成USART2

随机推荐

  • SPI的CRC校验计算

    22 3 6 CRC计算 CRC校验仅用于保证全双工通信的可靠性 数据发送和数据接收分别使用单独的CRC计算器 通过对每一个接收位进行可编程的多项式运算来计算CRC CRC的计算是在由SPI CR1寄存器 中CPHA和CPOL位定义的采样时
  • 记录JPA并发save时遇到的坑

    前言 在JPA中 xff0c 使用save方法时是这样的 xff1a 如果我们save的对象指定了主键 xff0c 那么会根据主键先进行一次查询 xff0c 如果查询记录不存在则执行insert语句 xff0c 如果查询记录存在则执行upd
  • Openmv(一)OpenMV图像处理的基本方法

    一 图像处理基础知识 摄像头 xff1a 光学信号转换成电信号 计算机视觉中 xff0c 最简单的模型是小孔成像模型 小孔成像是一种理想模型 xff0c 实际镜头会存在场曲和畸变等 xff0c 但可以通过在标定过程中引入畸变参数解决 xff
  • CMakeLists详解

    CMakeLists详解 一 CMake简介 cmake 是一个跨平台 开源的构建系统 它是一个集软件构建 测试 打包于一身的软件 它使用与平台和编译器独立的配置文件来对软件编译过程进行控制 二 常用命令 1 指定cmake最小版本 cma
  • c++继承与多态总结

    不知不觉C 43 43 课程的学习已经接近尾声 xff0c 感觉自己对于c 43 43 的认知更近了一步 xff0c 粗略总结一下最近学习的继承与多态部分的知识 继承 C 43 43 的继承 继承有3种形式 xff1a 私有继承 保护继承
  • C++对象的销毁

    对象的销毁 一般来说 xff0c 需要销毁的对象都应该做清理 解决方案 1 为每个类都提供一个public的free函数 xff1b 2 对象不再需要时立即调用free函数进行清理 析构函数 1 C 43 43 的类中可以定义一个特殊的清理
  • C++中类中的函数重载

    类中的函数重载 函数重载的回顾 1 函数重载的本质就是为相互独立的不同函数 xff1b 2 C 43 43 中通过函数名和函数参数确定函数调用 xff1b 3 无法直接通过函数名得到重载函数的入口地址 xff1b 4 函数重载必然发生在同一
  • C++中的字符串类

    字符串类 历史遗留的问题 1 C语言不支持真正意义上的字符串 xff1b 2 C语言用字符数组和一组实现字符串操作 xff1b 3 C语言不支持自定义类型 xff0c 因此无法获得字符类型 xff1b 解决方案 1 从C到C 43 43 的
  • MySQL中的Block Nested Loop优化分析

    前言 一般在MySQL规范中 xff0c 都会规定如果两张表进行join查询 xff0c 那么join的字段一定要有索引 xff0c 在之前的文章中我们分析了MySQL join大小表前后顺序影响分析 xff0c 这是在有索引的情况下 xf
  • C++之类模板的概念和意义

    类模板 一些类主要用于存储和组织数据元素 类中数据组织的方式和数据元素的具体类型无关 如 xff1a 数组类 链表类 Stack Queue类 等 1 C 43 43 中将模板的思想应用于类 xff0c 使得类的实现不关注数据元素的具体类型
  • C++之单例类模板

    需求的提出 在架构设计时 xff0c 某些类在整个系统生命周期中最多只能有一个对象存在 xff08 Single Instance xff09 要控制类的对象数目 xff0c 必须对外隐藏构造函数 xff1b 思路 xff1a 1 将构造函
  • 【无标题】

    绘图控件GraphicsView 一 GraphicsView简介 1 QT有多种绘图相关的技术 xff0c 我们将在第2部分 2 4 QT绘图和图表 中比较详细系统的讲 2 本节简单讲一下GraphicsView的基本理论 xff0c 并
  • uboot源码分析之start.S解析

    1 start S引入 1 1 u boot lds中找到start S入口 1 在uboot中因为有汇编阶段参与 xff0c 因此不能直接找main c 整个程序的入口取决于链接脚本中ENTRY声明的地方 ENTRY start 因此 s
  • uboot启动第二阶段

    uboot启动第二阶段 start armboot函数简介 一个很长的函数 1 这个函数在uboot lib arm board c的第444行开始到908行结束 2 450行还不是全部 xff0c 因为里面还调用了别的函数 3 为什么这么
  • cmake设置编译类型为release命令

    cmake编译类型通常默认为debug xff0c 但是在编译软件时 xff0c 一般都需要使用release版本的 xff0c debug太慢了 设置为release版本可以在cmake文件里进行 xff0c 也可以在运行cmake命令时
  • 设计模式之单例模式(Singleton),以C++为例,实现日志输出。

    Hello大家好 xff0c 好久没更新了 xff0c 今天给大家补上最基础的设计模式 xff1a 单例模式 这个单例模式实在是我的心结啊 xff0c 2021年末左右面试京东算法岗 xff0c 面试官让我写一个单例 xff0c 没写出来
  • 源码分析MyBatis对数值(int、double)类型进行test判断的误区

    文章目录 问题描述问题分析验证解析表达式执行解析后表达式分别测试两个条件 查询Ognl官方文档验证问题解决 问题描述 在如下判断中 xff0c 如果type类型为int xff0c 那么对于type 61 39 39 部分判断会出现一些问题
  • Git报错:error: xxxx bytes of body are still expected.

    git一个很老的项目 xff0c 项目深度很深 xff0c 报错 xff1a error 7857 bytes of body are still expected fetch pack unexpected disconnect whil
  • 设计模式之代理模式(Proxy),以C++为例,实现远程代理、虚拟代理、保护代理等。

    兄弟姐妹们好 xff0c 又是好久没有更新了 xff0c 今天给大家简单介绍代理模式 xff0c 一个很简单的设计模式 xff0c 旨在不改变原对象的情况下通过代理对象来控制对原对象的访问 代理模式根据具体情况还可以分为远程代理 虚拟代理
  • C++ 互斥锁原理以及实际使用介绍

    兄弟姐妹们 xff0c 我又回来了 xff0c 今天带来实际开发中都需要使用的互斥锁的内容 xff0c 主要聊一聊如何使用互斥锁以及都有哪几种方式实现互斥锁 实现互斥 xff0c 可以有以下几种方式 xff1a 互斥量 xff08 Mute