关于c++多线程中的互斥锁mutex

news2024/11/15 17:44:12

关于c++多线程中的互斥锁mutex

  • c++中的多线程
    • 线程的基本概念
    • C++ 标准库中的线程支持
  • 多线程与主线程与join
    • 换一种方式理解
    • 线程互斥锁
      • 第一种
      • 第二种
    • 子线程与互斥锁
    • 混合锁--看这个应该就明白了(这个主要使用的是嵌套锁)
      • 定义一个类
      • 创建线程
    • 这个示例主要使用并列锁
      • 重点
      • 并列锁示例
        • 类----锁对象和对应锁空间
        • push函数
        • pop函数
  • 总结

c++中的多线程

C++ 的多线程编程是指在一个程序中同时执行多个线程,以实现并行计算和提高程序的效率。线程是程序执行中的最小单位,每个线程都有自己的执行路径和栈空间,可以并行处理任务。C++ 提供了标准库支持,以简化多线程编程的过程。

线程的基本概念

线程:是轻量级的进程,它共享进程的资源(如内存和文件描述符),但有自己的执行路径和栈。
并发:在同一时间段内有多个线程在执行,可以在多核处理器上真正并行执行,也可以在单核处理器上通过时间片轮转实现并发执行。
同步:多个线程访问共享资源时需要保证数据一致性,这通常通过锁、条件变量等同步机制来实现。

C++ 标准库中的线程支持

C++11 引入了对多线程的标准支持,提供了线程库和同步机制。以下是一些关键组件:

std::thread:用于创建和管理线程。
std::mutex:用于线程间的互斥操作,防止多个线程同时访问共享资源。
std::lock_guard 和 std::unique_lock:用于自动管理互斥锁的加锁和解锁。
std::condition_variable:用于线程间的条件同步,允许线程在某些条件下进行等待和通知。
std::future 和 std::promise:用于线程间的异步任务和结果传递。

多线程与主线程与join

int main()
{
    SetConsoleOutputCP(936);
    string str = "123";
    for (int i = 0; i <3;)
    {
        /* code */
        que.push("e" + string(1,str[i]),ready);
        ++i;
    }
    cout << "nihaohiahc" << endl;


    // 多线程开发

    thread t1(add_element, "e4");
    thread t2(await);

    //开启线程
    t1.join();

    cout << "nihao " << endl;

    t2.join();
    cout << que.size()<<endl;
    cout << que.queue_array[0];
    cout << que.pop() << endl
         << que.front()<<endl;
    system("pause");
    system("cls");
}

这个主函数代表的就是主线程

   // 多线程开发

    thread t1(add_element, "e4");
    thread t2(await);

这两个代表的是子线程
一旦创建就开始自动运行

    //等待线程
    t1.join();

    cout << "nihao " << endl;

    t2.join();

join代表,主线程开始运行到t1.join的时候,必须得等待t1执行完毕,才能运行cout,但t2是线程。不会被影响,在线程创建的那一刻就开始被执行,同时t2.join代表主线程运行到这行代码也会被要求等待,

总结就是,线程一旦被创建立马就会被运行,主线程遇到join需要听下来等待相应的子线程运行完毕。

所以这里的nihao一定是在t1结束之后才会被输出

换一种方式理解


 thread t1(add_element, "e4");
 t1.join();
  thread t2(await);

在创建t1线程之后,子线程就开始自己跑自己的,主线程发现遇到t1.join那么就会停下来等待它执行完毕,之后主线程才开始创建t2子线程

线程互斥锁

通过包创建的 mutex 对象就是代表一种资源锁,也叫代码锁,也就是从当前写这个锁开始到这个锁生命结束中间的所有代码(资源)都属于这个对象

当然创建锁的方式有两种

第一种

unique_lock<mutex> temp_lock(que.mut);

我这里把que是队列对象,mut是mutex的对象,同时他也是que类的成员变量。

解释: unique_lock代表能够自动关开锁,mut是mutex锁对象,temp_lock是锁的对象,也就是具体资源的对象化。

还是用代码说明吧

void await()
{
    //可以手动开解锁
    unique_lock<mutex> temp_lock(que.mut);

    cout << "开始唤醒11111" << endl;
    que.tail_wait.wait(temp_lock);

    cout<< "开始唤醒" << endl;
    que.tail_wait.notify_one();

}

在这个函数作用域内,我们生成了一个资源锁对象temp_lock,锁的资源就是,直到这个对象死去或者进入等待状态(que.tail_wait.wait(temp_lock);),就会解开锁

锁的名称是mut,资源管理者就是temp_lock,对此等待操作的是资源管理者

第二种

void add_element(string x)
{

    Sleep(5000);
    que.mut.lock();
    // unique_lock<mutex> temp_lock(que.mut);
    cout << "开始添加元素之前" << endl;
    que.push(x,ready);
    que.mut.unlock();
}

直接用锁mut锁住这些资源,然后直到解锁unlock为止

子线程与互斥锁

子线程代表是我们创建的很多个线程函数
互斥锁代表锁进程的对象

因此当线程开始运行的时候,他们都需要去争抢一把线程锁,当一个线程获取到对应的互斥锁,那么该锁下面的其他子线程需要等待,获取到该锁线程释放锁

释放锁的方式有两种:第一进行等待行列。第二生命结束或者被人unlock开锁

混合锁–看这个应该就明白了(这个主要使用的是嵌套锁)

定义一个类

class queue_arary
{
public:
    queue_arary(int capacity);
    ~queue_arary();
    void print();
    int size()
    {
        return this->queue_size;
    }
    bool is_full();
    string front();

    string pop();
    void push(string data,bool& ready);
    bool empty();
    int queue_capacity;
    int queue_size;
    int head_index;
    string *queue_array;
    condition_variable_any tail_wait;// 生产者也就是push的人
    condition_variable_any head_wait;// 消费者也就是pop的人
    mutex queue_lock;//允许常量成员函数修改变量
    mutex mut;
};

其中包含两种锁 mutex queue_lock;//允许常量成员函数修改变量
mutex mut;

包含一个简单的push函数后面用来讲解混合锁

inline void queue_arary::push(string data,bool& ready)
{
    // 自动加锁和解锁
    unique_lock<mutex> Lock(queue_lock);

    // 手动开启关闭
    // 保证多线程也不会乱出错
    try
    {
        // 可以让线程进入等待
        // 直到队列有空闲位置,避免虚假唤醒
        while (this->is_full())
        {
            // /* code */
            // if (this->tail_wait.wait_for(Lock, std::chrono::seconds(1)) == std::cv_status::timeout)
            // {
            //     /* code */
            //     cout << "Timeout, performing action" << endl;
            //     return;
            ready = true;
            // {unique_lock<mutex> notify_lock(this->mut);
            this->mut.unlock();
            this->tail_wait.notify_all();
            tail_wait.wait(Lock);
            cout << "Queue is full, wait for pop" << endl;
           
            return;
            }
        this->queue_array[this->queue_size] = data;
        this->queue_size++;
        cout<<"push success"<<endl;
        // 通知其他线程
        head_wait.notify_one();
    }
    catch (...)
    {
       // 确保异常情况下也能解锁
        throw;
    }

}

创建线程


    // 多线程开发

    thread t1(add_element, "e4");
    thread t2(await);
    
void add_element(string x)
{

    Sleep(5000);
    que.mut.lock();
    // unique_lock<mutex> temp_lock(que.mut);
    cout << "开始添加元素之前" << endl;
    que.push(x,ready);

}

void await()
{
    //可以手动开解锁
    unique_lock<mutex> temp_lock(que.mut);

    cout << "开始唤醒11111" << endl;
    que.tail_wait.wait(temp_lock);

    cout<< "开始唤醒" << endl;
    que.tail_wait.notify_one();

}

首先采用sleep使得t1的线程缓慢(当然这是我人为的不推荐),争抢不过t2,因此t2获得mut互斥锁

接着t2带着互斥锁mut进行到 que.tail_wait.wait(temp_lock);进入tail_wait的等待空间中,此时因为等待从而失去互斥锁mut

之后t1拿到mut互斥锁的权限que.mut.lock();,进行上锁,开始执行到push函数,但因为add_element函数作用域没有消失,因此t1现在最外面还有一把mut的大锁

接着进入push函数,遇到另一把queue_block的锁,对此mut下面有一把queue_block的锁,直到遇见 this->mut.unlock();,开始释放掉他的mut锁

最重要的一点,释放掉mut锁之后,开始唤醒这个等待空间的线程,线程就会去获取锁,这个时候发现锁只有mut,那么就进行t2线程的唤醒,
当然这个唤醒进程得等queue_block解锁之后才行,也就是进入到等待空间

这个时候t2,获取到锁,开始执行唤醒等待空间的线程,当然这也是要等解锁完成后才可以,

最后t1又获取到queue_block的锁,继续执行直到解锁也就是跳出作用域

流程如下
在这里插入图片描述

对此结束

这个示例主要使用并列锁

重点

一个等待空间的唤醒需要在对应锁变量作用域下在能够唤醒对应的等待空间
比如

inline void queue_arary::push(string data)
{
    // 自动加锁和解锁
    unique_lock<mutex> Lock(this->push_block);
    try
    {
        // 可以让线程进入等待
        // 直到队列有空闲位置,避免虚假唤醒
        while (this->is_full())
        {
            // 超时行为
            // if (this->tail_wait.wait_for(Lock, std::chrono::seconds(4)) == std::cv_status::timeout)
            // {
            //     /* code */
            //     cout << "Timeout, performing action" << endl;
            //     return;
            // }

            tail_wait.wait(Lock);
        }
        this->queue_array[this->tail_index] = data;
        this->tail_index = (this->tail_index + 1) % this->queue_capacity;
        this->qsize++;
        cout << "push success" << endl;
        // 通知其他线程

        //只使用空的时候进行加锁解锁和通知
        if (this->qsize == 1)
        { 
            /* code */
            this->push_block.unlock();
            this->pop_block.lock();
            this->head_wait.notify_one();
            this->pop_block.unlock();
        }
        if (this->qsize<this->queue_capacity)
        {
            /* code */
            this->tail_wait.notify_one();
        }
        
        
    }
     catch(...)
    {
        // 确保异常情况下也能解锁
        throw;
    }
}

其中unique_lock Lock(this->push_block);是push_block的锁
要想唤醒 另一个等待空间,需要创建对应的锁作用域

 this->push_block.unlock();
            this->pop_block.lock();
            this->head_wait.notify_one();
            this->pop_block.unlock();

并列锁示例

结合上面的重点,观察锁作用域和等待唤醒空间

类----锁对象和对应锁空间
class queue_arary
{
public:
    queue_arary(int capacity);
    ~queue_arary();
    void print();
    int size()
    {
        return this->qsize;
    }
    bool is_full();
    string front();

    string pop();
    void push(string data);
    bool empty();
    int queue_capacity;
    int head_index;
    int tail_index;
    string *queue_array;

    //原子变量
    atomic<int> qsize;

    //等待空间
    condition_variable_any tail_wait; // 生产者也就是push的人
    condition_variable_any head_wait; // 消费者也就是pop的人
   //队列锁
    mutex push_block;                  // 允许常量成员函数修改变量
    mutex pop_block;                  // 允许常量成员函数修改变量
};

其中的锁对象与对应的唤醒空间如下

 //等待空间
    condition_variable_any tail_wait; // 生产者也就是push的人
    condition_variable_any head_wait; // 消费者也就是pop的人
   //队列锁
    mutex push_block;                  // 允许常量成员函数修改变量
    mutex pop_block;                  // 允许常量成员函数修改变量

关于并列锁,我们在使用的时候就是防止死锁,对此在使用的时候,要提前解锁,方便唤醒其他的等待空间

push函数
inline void queue_arary::push(string data)
{
    // 自动加锁和解锁
    unique_lock<mutex> Lock(this->push_block);
    try
    {
        // 可以让线程进入等待
        // 直到队列有空闲位置,避免虚假唤醒
        while (this->is_full())
        {
           
            tail_wait.wait(Lock);
        }
        this->queue_array[this->tail_index] = data;
        this->tail_index = (this->tail_index + 1) % this->queue_capacity;
        this->qsize++;
        cout << "push success" << endl;
        // 通知其他线程

        //只使用空的时候进行加锁解锁和通知
        if (this->qsize == 1)
        { 
            /* code */
            this->push_block.unlock();
            this->pop_block.lock();
            this->head_wait.notify_one();
            this->pop_block.unlock();
        }
        if (this->qsize<this->queue_capacity)
        {
            /* code */
            this->tail_wait.notify_one();
        }
        
        
    }
     catch(...)
    {
        // 确保异常情况下也能解锁
        throw;
    }
}

其中锁对象unique_lock Lock(this->push_block);等待空间为 tail_wait.wait(Lock);
我们需要唤醒的是pop函数中的等待空间,

表示只要我们添加了一个push元素在队列中,那么就可以先解锁当前push_block在唤醒pop_block的等待空间(需要包含在锁作用域下面)

pop函数
inline string queue_arary::pop()
{
    unique_lock<mutex> Lock(this->pop_block);
    // 手动开启关闭

    while (this->qsize == 0)
    {
       
        head_wait.wait(Lock);
    }

    // 利用copy的方法进行元素的移动
    // std:: this_thread::sleep_for(chrono::seconds(10));
    string temp = this->queue_array[this->head_index];
    this->queue_array[this->head_index] = "";
    this->head_index = (this->head_index + 1) % this->queue_capacity;
    this->qsize--;
    // 通知其他线程
    if (this->qsize!=0)
    {
        /* code */
        head_wait.notify_one();
    }

   //一次pop唤醒所有push,从满到不满
    if(this->qsize+1==this->queue_capacity)
    {
    pop_block.unlock();
    push_block.lock();
    tail_wait.notify_one();
    push_block.unlock();    

    }
 
    return temp;

当pop_block的等待空间被唤醒的时候,一次的pop可以唤醒下面的自己 head_wait.notify_one();的等待空间所有pop线程,

if (this->qsize!=0)
    {
        /* code */
        head_wait.notify_one();
    }

而对应的当一次pop可以解锁,在锁作用域下面唤醒对应的等待空间,也就是push_block空间

同理在push函数中,一次被唤醒,可以唤醒自己所有等待空间的push_block线程

  if (this->qsize<this->queue_capacity)
        {
            /* code */
            this->tail_wait.notify_one();
        }

总结

嵌套锁一定要在把所有的锁从里到外一个一个解锁才行(最里面的解锁一般是变成等待空间之后会释放锁,这个时候瞬间执行,区间代码,也就是mut.unlock()这样解开最外面的锁)

并列锁一定要在调用另一个等待唤醒空间的时候,进行解锁,在创建锁作用域,方便唤醒对应的锁等待空间
至此希望能够对大家有所帮助,如有不懂的,评论区见

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2056919.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP负库存

业务示例 在系统中&#xff0c;对于一些物料而言&#xff0c;不能立即将收到的交货输入为收货。如果要使发货无论如何都是可以过帐的&#xff0c;则需要允许这些物料的负库存。 负库存 发货数量大于预订数量时&#xff0c;过帐该发货就会出现负库存。如果由于组织原因&#…

【人工智能】Transformers之Pipeline(十一):零样本图片分类(zero-shot-image-classification)

目录 一、引言 二、零样本图像分类&#xff08;zero-shot-image-classification&#xff09; 2.1 概述 2.2 技术原理 2.3 应用场景 2.4 pipeline参数 2.4.1 pipeline对象实例化参数 2.4.2 pipeline对象使用参数 2.4 pipeline实战 2.5 模型排名 三、总结 一、引言 …

嵌入式软件--PCB DAY 1

一、入门 1.什么是PCB 随着技术的进步&#xff0c;我们已经可以将一个电子设备的主要功能全部集成在一块单独的电路板上。这种电路板可以由相关的机械设备像印刷一样生产出来。因此我们现在的电路板可以被称为印刷电路板(Printed Circuit Board&#xff09;。 2.什么是PCBA …

洛谷B3981题解

题目描述 &#xff08;你不需要看懂这张图片&#xff1b;但如果你看懂了&#xff0c;会觉得它很有趣。&#xff09; JavaScript 是一种功能强大且灵活的编程语言&#xff0c;也是现代 Web 开发的三大支柱之一 (另外两个是 HTML 和 CSS)。灵活的 JavaScript 包含“自动类型转换…

C++实现——红黑树

目录 1.红黑树 1.1红黑树的概念 1.2红黑树的性质 1.3红黑树节点的定义 1.4红黑树的插入操作 1.5红黑树的验证 1.6红黑树的删除 1.7红黑树与AVL树的比较 1.8红黑树的应用 1.红黑树 1.1红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位…

系统架构:分而治之

系统架构&#xff1a;分而治之 引言“分而治之”在架构中的应用模块化设计分层化架构微服务架构 分而治之的优势降低复杂性提高灵活性和可扩展性增强可维护性促进团队协作 分而治之的劣势复杂性转移性能开销开发和运维的复杂性数据一致性挑战 结论 引言 “分而治之”是一种分析…

修改Patroni ttl和retry_timeout

参数解释 修改 & 查看 https://www.cnblogs.com/linyouyi/p/15714010.html

58.区间和

58.区间和 //卡码网题号58.区间和 /* //如果我查询m次&#xff0c;每次查询的范围都是从0到n-1&#xff0c;那么该算法的时间复杂度是O(n*m)&#xff0c; //如果查询次数非常大的话&#xff0c;这个时间复杂度也是非常大的。 #include<iostream> #include<vector> …

失易得数据恢复体验,2024精选数据恢复工具推荐!

数据丢失的风险无处不在&#xff0c;可能是由于硬件故障、软件错误、病毒感染或人为操作失误等原因。在这种情况下&#xff0c;数据恢复工具就显得尤为重要。本文将介绍几款市场上广受好评的数据恢复工具&#xff0c;帮助您在数据丢失后能够迅速找回宝贵的信息。 一、Foxit数据…

Windows客户端加入域环境时提示指定的服务器无法运行请求的操作

工作中小毛小病之&#xff1a;如下图 问题出在域控制器上&#xff0c;检查域控制器的各项域服务是否正常&#xff0c;确认windows防火墙关闭&#xff0c;一般能解决这个问题&#xff1b; 如果之前一切正常&#xff0c;只是某台电脑重装系统或者新电脑加入域出现这个情况&#…

LCD 显示字符

1.0 字符显示 使用显示图片的方式显示字符会浪费存储空间&#xff0c;显示字符的时候字符的笔画是一个固定的颜色&#xff0c;因此不用使用显示图片的方式&#xff0c;可以使用1 表示字符的本身&#xff0c;0 表示字符的背景&#xff0c;使用这种方式显示字符节省存储空间。 注…

每日OJ_牛客_反转部分单向链表

目录 牛客_反转部分单向链表 解析代码 牛客_反转部分单向链表 反转部分单向链表__牛客网 题目给的代码‘&#xff1a; #include <iostream> using namespace std; struct Node {int val;struct Node* next; }; Node* input_List() {int n,val;Node* pheadnew Node();…

【Java】效率工具模板的使用

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 乱码问题4.2 快捷键模板4.3 文件模板 一、前言 提高效率 二、学习内容&am…

【开端】Linux抓包测试接口

一、绪论 平时我们开发接口&#xff0c;可以通过程序去调用接口测试接口的情况&#xff0c;也可以通过postman去测试接口的联通情况&#xff0c;也可以直接通过命令去调试接口的情况。 二、几种接口调试方式 1、程序代码测试 public static void main(String[] args) {String …

电子木鱼+提肛+游戏地图,车机还能这么玩?

文/王俣祺 导语&#xff1a;电子木鱼、提肛训练、游戏级地图&#xff0c;你很难想象这些“直男关怀”是来自小鹏MONA M03的车机系统。最近&#xff0c;一批关于MONA M03车机功能的视频在网上疯传&#xff0c;一系列“没用但有趣”的功能广受年轻用户的好评&#xff0c;情绪价值…

【Linux】搭建Openstack(一)

搭建openstack平台的总结 Openstack是一个开源的云计算平台&#xff0c;可以提供基础设施即服务&#xff08;IaaS&#xff09;的功能&#xff0c;让用户可以在自己的数据中心部署和管理虚拟化的资源。 Openstack是当今最具影响力的云计算管理工具——通过命令或者基于web的可…

PostgreSQL下载、安装(Windows 10/11 64位)详细教程【超详细,保姆级教程!!!】

本文介绍关于windows 11如何下载、安装PostgreSQL-15.8版本的详细步骤 一、下载PostgreSQL 1、进入官网 PostgreSQL下载地址&#xff08;官网&#xff09; 直达PostgreSQL下载页面&#xff08;官网&#xff09; 2、点击“Download the installer”链接&#xff0c;选择合适…

使用Seaborn绘制热力图

热力图是一种用于展示矩阵数据的图表&#xff0c;其中颜色深浅表示数据值的大小。 import seaborn as sns import numpy as np import matplotlib.pyplot as plt # 创建示例数据 data np.random.rand(10, 12) # 绘制热力图 sns.heatmap(data, annotTrue, cmapcoolwa…

Ubuntu20.04离线安装 Docker

1.下载3个docker离线安装包&#xff0c;下载网址&#xff1a; https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/2.把3个离线安装包拷贝到ubuntu本地执行以下命令 sudo dpkg -i containerd.io_1.4.6-1_amd64.deb sudo dpkg -i docker-ce-cli_20.10.…

蓝队技能-应急响应篇C2后门权限维持手法WindowsLinux基线检查排查封锁清理

知识点 1、应急响应-C2后门-排查&封锁 2、应急响应-权限维持-排查&清理 3、应急响应-基线检测-整改&排查演示案例-蓝队技能-C2后门&权限维持-基线检查&查杀封锁-Windows 1、常规C2后门-分析检测 无隐匿手法 也可以把怀疑的exe程序上传到沙箱上分析 有…