C++多线程:互斥锁

2023-05-16

目录

1.前言

2.互斥锁

2.1 互斥锁的特点

2.2 互斥锁的使用

2.2 std::lock_guard

3.死锁

3.1 死锁的含义

3.2 死锁的例子

3.3 死锁的解决方法


1.前言

       比如说我们现在以一个list容器来模仿一个消息队列,当消息来临时插入list的尾部,当读取消息时就把头部的消息读出来并且删除这条消息。在代码中就以两个线程分别实现消息写入和消息读取的功能,如下:

class msgList
{
private:
	list<int>mylist;   //用list模仿一个消息队列

public:
	void WriteList()   //向消息队列中写入消息(以i作为消息)
	{
		for (int i = 0; i<100000; i++)
		{
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
		}
		return;
	}
	void ReadList()  //从消息队列中读取并取出消息
	{
		for(int i=0;i<100000;i++)
		{
			if (!mylist.empty())
			{
				cout << "Read : " << mylist.front() << endl;
				mylist.pop_front();
			}
			else
			{
				cout << "Message List is empty!" << endl;
			}
		}
	}
};
int main()
{
	msgList mlist;
	thread pread(&msgList::ReadList, &mlist);   //读线程
	thread pwrite(&msgList::WriteList, &mlist);   //写线程
     //等待线程结束
	pread.join(); 
	pwrite.join();

    return 0;
}

        这段程序在运行过程中,大部分时间是正常的,但是也会出现如下不稳定的情况:

       为什么会出现这种情况呢?

       这是因为消息队列对于读线程和写线程来说是共享的,这时就会出现两种特殊的情况:读线程的读取操作还没有结束,线程上下文就切换到了写线程中;或者写线程的写入操作还没有结束,线程上下文切换就到了读线程中,这两种情况都反映了读写冲突,从而出现了以上错误。

       要想解决这个问题,最显然最直接的方法就是将读写操作分离开来,读的时候不允许写,写的时候不允许读,这样,才能实现线程安全的读和写。说形象一点,就是在进行读操作时,就对共享资源进行加锁,禁止其他线程访问,其他线程要访问就得等到读线程解锁才行,就像上厕所一样,一次只能上一个人,其他人必须得等他上完了再上。这样,就有了互斥锁的概念。

2.互斥锁

       在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。比如说,同一个文件,可能一个线程会对其进行写操作,而另一个线程需要对这个文件进行读操作,可想而知,如果写线程还没有写结束,而此时读线程开始了,或者读线程还没有读结束而写线程开始了,那么最终的结果显然会是混乱的。为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )

2.1 互斥锁的特点

1. 原子性:把一个互斥量锁定为一个原子操作,这意味着如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量;

2. 唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量;

3. 非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

2.2 互斥锁的使用

       根据前面我们可以知道,互斥锁主要就是用来保护共享资源的,在C++ 11中,互斥锁封装在mutex类中,通过调用类成员函数lock()和unlock()来实现加锁和解锁。值得注意的是,加锁和解锁,必须成对使用,这也是比较好理解的。除此之外,互斥量的使用时机,就以开篇程序为例,我们要保护的共享资源当然就是消息队列list了,那么互斥锁应该加在哪里呢?

       可能想的比较简单一点:就直接把锁加在函数最前面不就好了么?如下所示:

class msgList
{
private:
	list<int>mylist;   //用list模仿一个消息队列
        mutex mtx;   //创建互斥锁对象
public:
	void WriteList()   //向消息队列中写入消息(以i作为消息)
	{
                mtx.lock();
		for (int i = 0; i<100000; i++)
		{
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
		}
                mtx.unlock();
		return;
	}
	//.......
};

        不过如果这样加锁的话,要等写线程完全执行结束才能开始读线程,读写线程变成了串行执行,这就违背了线程并发性的特点了。正确的加锁方式应当是在执行写操作的具体部分加锁,如下所示:

class msgList
{
private:
	list<int>mylist;   //用list模仿一个消息队列
        mutex mtx;   //创建互斥锁对象
public:
	void WriteList()   //向消息队列中写入消息(以i作为消息)
	{   
		for (int i = 0; i<100000; i++)
		{
                        mtx.lock();
			cout << "Write : " << i <<endl;
			mylist.push_back(i);
                        mtx.unlock();
		}
		return;
	}
	//.......
};

         这样,才能真正的实现读写互不干扰。

        下面再举一个更为直观的例子,创建两个线程同时对list进行写操作:

class msgList
{
private:
	list<int>mylist;
	mutex m;
	int i = 0;
public:
	void WriteList()
	{
		while(i<1000)
		{
			mylist.push_back(i++);
		}
		return;
	}
	void showList()
	{
		for (auto p = mylist.begin(); p != mylist.end(); p++)   
		{
			cout << (*p) << " ";
		}
		cout << endl;
		cout << "size of list : " << mylist.size() << endl;
		return;
	}
};
int main()
{
	msgList mlist;
	thread pwrite0(&msgList::WriteList, &mlist);
	thread pwrite1(&msgList::WriteList, &mlist);

	pwrite0.join();
	pwrite1.join();
	cout << "threads end!" << endl;

	mlist.showList();  //子线程结束后主线程打印list
    return 0;
}

        这里用两个线程来写list,并且最终在主线程中调用了showList()来输出list的size和所有元素,我们先来看下输出情况:

       根据结果可以看到,这里有很多问题:实际输出的元素个数和size不符,输出的元素也并不是连续的,这都是多个线程同时更新list所造成的情况。这种情况下,运行结果是无法预料的,每次都可能不一样。这就是线程不安全所引发的问题,我们加上锁再来看看:

class msgList
{
private:
	list<int>mylist;
	mutex m;
	int i = 0;
public:
	void WriteList()
	{
		while(i<1000)
		{
                        m.lock();//加锁
			mylist.push_back(i++);
                        m.unlock(); //解锁
		}
		return;
	}
	// ......
};

        这样加锁就正确了吗?我们再多运行几次看看:

        数字都是连续的,但是个数却多了一个(出现的几率还是比较小),这又是什么原因造成的呢?还是两个线程的问题,假设要插入1000个数,循环条件就是while(i<1000),当i=999的时候两个写线程都可以进入while循环,此时如果pwrite0线程拿到了lock(),那么pwrite1线程就只能一直等待,pwrite0线程继续往下执行,使得i变成了1000,此时,对于pwrite0线程来说,它就必须退出循环了。而此时的pwrite1在哪里呢?还等在lock()的地方,pwrite0线程unlock()后,pwrite1成功lock(),此时i=1000,但是pwrite1却还没有执行完此次循环,因此向list中插入1000,此时退出的i的值为1001,这也就造成了实际输出为1001个数的情况。

       为了避免这个问题,一个简单的办法就是在lock()之后再加上一个判断,判断i是否依旧满足while的条件,如下:

void WriteList()
	{
		while(i<10000)
		{
			m.lock();
			if (i >= 10000)
			{
				m.unlock();   //退出之前必须先解锁
				break;
			}
			mylist.push_back(i++);
			m.unlock();
		}
		return;
	}

         为什么这里要在break前面加一个unlock()呢?原因就在于:如果break前面没有unlock(),一旦i符合了if的条件,就直接break了,此时就没法unlock(),程序就会报错:

        可以发现,这种错误是比较难发现的,特别是像这样程序中出现了分支的情况,很容易就使得程序实际运行时lock()了却没有unclock()。为了解决这一问题,就有了std::lock_guard。

2.2 std::lock_guard

      简单来理解的话,lock_guard就是一个类,它会在其构造函数中加锁,而在析构函数中解锁,也就是说,只要创建一个lock_guard的对象,就相当于lock()了,而该对象析构时,就自动调用unlock()了。

      就以上述程序为例,直接改写为:

void WriteList()
	{
		while(i<10000)
		{
                        lock_guard<mutex> guard(m);  //创建lock_guard的类对象guard,用互斥量m来构造
			//m.lock();   
			if (i >= 10000)
			{
				//m.unlock();   //由于有了guard,这里就无需unlock()了
				break;
			}
			mylist.push_back(i++);
			//m.unlock();
		}
		return;
	}

       这里主要有两个需要注意的地方:第一、原先的lock()和unlock()都不用了;第二、if中的break前面也不用再调用unlock()了。这都是因为对象guard在lock_guard一句处构造出来,同时就调用了lock(),当退出while时,guard析构,析构时就调用了unlock()。(局部对象的生命周期就是创建该对象时离其最近的大括号的范围{})

3.死锁

3.1 死锁的含义

        死锁是什么意思呢?举个例子,我和你手里都拽着对方家门的钥匙,我说:“你不把我的锁还来,我就不把你的锁给你!”,你一听不乐意了,也说:“你不把我的锁还来,我也不把你的锁给你!”就这样,我们两个人互相拿着对方的锁又等着对方先把锁拿来,然后就只能一直等着等着等着......最终谁也拿不到自己的锁,这就是死锁。

       显然,死锁是发生在至少两个锁之间的,也就是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行,当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

3.2 死锁的例子

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
		lock_guard<mutex> g0(m0);  //线程0加锁0
		lock_guard<mutex> g1(m1);  //线程0加锁1
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
		lock_guard<mutex> g1(m1);  //线程1加锁1
		lock_guard<mutex> g0(m0);  //线程1加锁0
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
    return 0;
}

        我们来看下运行结果:

       这就出现了死锁。产生的原因就是因为在线程0中,先加锁0,再加锁1;在线程1中,先加锁1,再加锁0;如果两个线程之一能够完整执行的话,那自然是没有问题的,但是如果某个时刻,线程0中刚加锁0,就上下文切换到线程1,此时线程1就加锁1,然后此时两个线程都想向下执行的话,线程1就必须等待线程0解锁0,线程0就必须等待线程1解锁1,就这样两个线程都一直阻塞着,形成了死锁。

3.3 死锁的解决方法

①按顺序加锁

       以上述例程来说,就是线程0和线程1的加锁顺序保持一致,如下所示:

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
		lock_guard<mutex> g0(m0);  //线程0加锁0
		lock_guard<mutex> g1(m1);  //线程0加锁1
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
                lock_guard<mutex> g0(m0);  //线程1加锁0
		lock_guard<mutex> g1(m1);  //线程1加锁1
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
    return 0;
}

       在这种情况下,两个线程一旦一个加了锁,那么另一个就必定阻塞,这样,就不会出现两边加锁两边阻塞的情况,从而避免死锁。

②同时上锁

        同时上锁需要用到lock()函数,如下所述:

mutex m0,m1;
int i = 0;
void fun0()
{
	while (i < 100)
	{
                lock(m0,m1);
		lock_guard<mutex> g0(m0, adopt_lock);
		lock_guard<mutex> g1(m1, adopt_lock);
		cout << "thread 0 running..." << endl;
	}
	return;
}
void fun1()
{
	while (i < 100)
	{
                lock(m0,m1);
		lock_guard<mutex> g0(m0, adopt_lock);
		lock_guard<mutex> g1(m1, adopt_lock);
		cout << "thread 1 running...   "<< i << endl;
	}
	return;
}
int main()
{
	thread p0(fun0);
	thread p1(fun1);
	p0.join();
	p1.join();
    return 0;
}

       注意到这里的lock_guard中多了第二个参数adopt_lock,这个参数表示在调用lock_guard时,已经加锁了,防止lock_guard在对象生成时构造函数再次lock()。

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

C++多线程:互斥锁 的相关文章

  • Arduino修改Serial接收缓冲区大小

    看到网上有资料说 xff0c 直接添加以下宏定义就可以了 xff1a span class token macro property span class token directive keyword define span SERIAL
  • RT-Thread nmealib库WH-GN100模块设置仅支持北斗

    RT Thread nmealib库主页 在nmea thread init函数的末尾 xff0c 添加以下代码块 xff0c 发送配置指令 xff0c 仅使用北斗卫星 xff0c 即可配置成仅GPS卫星工作模式 span class to
  • C#中字符串判断为空或者空格

    最近遇到这个问题 xff0c 来大概说一下C 中字符串判断为空或者空格这个问题 xff08 1 xff09 字符串为空null xff0c 怎么讲就是内存中没有放东西 xff0c 比如新创建的字符串就为空null xff0c string
  • 【冷知识】火车票座位分布知识点

    最近到了每年过年 xff0c 春运火车高峰期的时候了 xff0c 有的人想知道自己具体的位置在哪里 xff08 比如硬座是不是靠窗的 xff0c 座位的大小号排序等 xff09 xff0c 现在来讲讲这方面的知识点 xff0c 个人整理 列
  • QT中的自定义信号以及自定义函数

    信号与槽函数是QT的一大创新 xff0c 通过自定义信号与槽函数可以实现自己想实现的功能 标准的信号与槽写法如下 xff1a connect amp button amp QPushButton clicked this amp QWidg
  • 如何摆放PCB元器件?(建议收藏)

    PCB设计 xff0c 既是科学也是艺术 其中有非常多关于布线线宽 布线叠层 原理图等等相关的技术规范 xff0c 但当你涉及到PCB设计中具有艺术特质元器件布局问题时 xff0c 问题就变得有趣起来了 事实上 xff0c 关于元器件摆放限
  • 【MFC开发(6)】复选框按钮控件Check Box

    1 新建复选框 直接拖拽即可 xff0c 设置名字可修改caption内容 2 设置默认选中 复选框可多选 xff0c 所以可以给很多复选框按钮进行选中 xff0c 代码如下所示 xff0c 放在dlg初始化函数中实现 获取多选框香蕉的指针
  • 【MFC开发(15)】进度条控件Progress Control

    1 进度条控件的常用方法 首先给控件添加一个变量 在dlg初始化函数钟进行方法的实现 进度条显示区域 设置进度条的范围 m progress SetRange 0 100 设置进度条当前的位置 m progress SetPos 75 获取
  • 【MFC开发(16)】树形控件Tree Control

    1 树形控件的属性配置 xff08 1 xff09 Check Boxes xff1a 默认为false xff0c 如果选择为true的话每个节点前面会带有一个方框 xff08 2 xff09 Edit Labels xff1a 默认为f
  • 【MFC开发(17)】高级列表控件List Control

    1 介绍 ListCtrl 高级列表控件也是我们平时编程过程中很常用的一个控件 xff0c 一般涉及到报表展示 记录展示之类的 xff0c 都需要ListCtrl 高级列表控件 例如 xff1a 任务管理器啊 xff0c 文件列表啊 xff
  • STM32L4单片机连接语音模块NVC的源码

    这周写了一下STM32L4的语音模块 xff0c 使用的语音芯片是NVC系列芯片 xff0c 提供一下代码给以后需要的朋友们 xff0c 不喜勿喷 头文件NVC h ifndef NVC H define NVC H 音源 define S
  • oled显示模块ssd1306

    管脚定义 GND 电源地 VCC xff1a 供电电源3 3v 5v都可以 D0 xff1a 串行输入时钟CLK D1 xff1a 串行输入数据 RES xff1a 复位 DC xff1a 控制输入数据 命令 xff08 高电平1为数据 低
  • 上位机串口数据检验方式(一)——校验和

    最近还是在写上位机软件 xff0c 还是有一堆问题 xff0c 因为是第一次做这个东西 xff0c 有些东西只能到论坛上来查 xff0c 最近做到了数据通信 xff0c 刚开始没有想到数据协议这些东西 xff0c 现在涉及到了 xff0c
  • c#上位机开发(三)——串口通信上位机开发1

    今天主要做一个跟市面上差不多的稍微简单点的上位机软件 xff0c 效果如下图所示 1 功能概述 xff08 1 xff09 端口扫描 xff0c 主要是扫描出可用的端口用来连接 xff08 2 xff09 波特率的选择 xff0c 使用一个
  • 使用python执行外部命令subprocess

    1 使用python执行外部命令subprocess subprocess模块是Python自带的模块 xff0c 无须再另行安装 xff0c 它主要用来取代一些旧的模块或方法 xff0c 如os system os spawn os po
  • #Qt on android#使用Qt 获取GPS信号

    注意事项 xff1a 1 Qt版本一定要大于等于5 3 xff0c 因为低于5 3的版本对于android系统来说并不能成功获取gps信号 2 环境正确搭建 xff0c 一定要注意 xff01 构建 xff08 build xff09 的系
  • 2023年TI杯全国大学生电子设计竞赛通知正式发布

    关于组织2023年 全国大学生电子设计竞赛的通知 xff08 电组字 2023 01号 xff09 各赛区组织委员会 各有关高等学校 xff1a 全国大学生电子设计竞赛 xff08 以下简称全国竞赛 xff09 组委会在认真总结往届电子设计
  • HTTPClient调用https请求,通过基本认证用户名密码(Basic Auth)

    本文来源是Apache官网例子 xff1a httpcomponents client ClientAuthentication java at 4 5 x apache httpcomponents client GitHub 之前找过很
  • c中结构体数据对齐问题

    1 为什么需要数据对齐 提升CPU读取数据的效率 CPU每次都是从以4字节 xff08 32位CPU xff09 或是8字节 xff08 64位CPU xff09 的整数倍的内存地址中读进数据的 xff08 例如32位的只能0x000000
  • js打开新窗口的方法总结

    Window open 方法 完整代码 window span class token punctuation span span class token function open span span class token punctu

随机推荐

  • 一文详解堆栈(二)——内存堆与内存栈

    前言 xff1a 我们经常听见一个概念 xff0c 堆 xff08 heap xff09 和栈 xff08 stack xff09 xff0c 其实在数据结构中也有同样的这两个概念 xff0c 但是这和内存的堆栈是不一样的东西哦 xff0c
  • 《动手学ROS2》3.2ROS2工作空间介绍

    本系列教程作者 xff1a 小鱼 公众号 xff1a 鱼香ROS QQ交流群 xff1a 139707339 教学视频地址 xff1a 小鱼的B站 完整文档地址 xff1a 鱼香ROS官网 版权声明 xff1a 如非允许禁止转载与商业用途
  • Ubuntu18.04 realsenseD435i深度摄像头外参标定的问题

    Ubuntu18 04 realsenseD435i深度摄像头外参标定的问题 鱼香ROS介绍 xff1a 鱼香ROS是由机器人爱好者共同组成的社区 xff0c 欢迎一起参与机器人技术交流 进群加V xff1a fishros2048 文章信
  • STM-32:USART串口协议、串口外设—数据发送/数据发送+接收

    目录 一 串口通信1 1通信接口1 2串口通信1 2 1简介1 2 2硬件电路1 2 3串口参数及时序 二 STM32的USART外设2 1USART简介2 2USART框图 三 数据传输3 1数据帧3 2输入数据策略3 2 1起始位侦测3
  • 大疆M3508、M2006必备CAN总线知识与配置方法

    大疆M3508 M2006必备CAN总线知识与配置方法 文章目录 大疆M3508 M2006必备CAN总线知识与配置方法前言 xff1a 0x00 需要 额外的 CAN收发器 xff01 xff01 xff01 0x01 硬件层面分析为什么
  • 换个角度聊聊PID吧,很干。

    01 前言 大家好 xff0c 前面发了几篇关于PID的文章 xff1a 点击图片即可阅读 教你10分钟完成智能小车的PID调速 快速调试PID参数的3种方法 02 自动控制系统 在直流有刷电机的基础驱动中 xff0c 如果电机负载不变 x
  • linux发起http请求,GET、POST

    GET请求 curl 推荐 curl v 34 https test com login username 61 tyw amp password 61 123 34 curl 34 https test com 34 URL指向的是一个文
  • VSCode对C++的DEBUG调试配置

    C 43 43 vscode上的调试配置 1 调试配置2 修改编译模式 按照本 的流程可在vscode平台上实现像在windows系统下VS调试C 43 43 程序的效果 1 调试配置 当写好代码和 CMakeLists txt 之后 xf
  • VSCode的C/C++扩展功能

    VSCode的C C 43 43 扩展功能 1 在 Linux 上 使用 C 43 43 1 1 创建 Hello World1 2 探索 IntelliSense1 3 构造 helloworld cpp1 3 1 运行 build1 3
  • 从源码理解智能指针(二)—— shared_ptr、weak_ptr

    目录 计数器 Ref count Ref count del Ref count del alloc Ptr base Ptr base的成员变量 构造函数 赋值重载 获取引用计数 减少引用计数 Reset函数 Resetw函数 share
  • muduo源码学习(1):异步日志——日志消息的存储及输出

    目录 前言 日志存储的实现 日志输出的实现 总结 前言 muduo中的日志 xff0c 是诊断日志 用于将代码运行时的重要信息进行保存 xff0c 方便故障诊断和追踪 日志一般有两种 xff0c 一种是同步日志 xff0c 一种是异步日志
  • muduo源码学习(2):异步日志——异步日志的实现

    目录 什么是异步日志 异步日志的实现 前端与后端 前端与后端的交互 资源回收 后端与日志文件 滚动日志 自动flush缓冲区 开启异步日志功能 总结 在前文中分析了日志消息的存储和输出 xff0c 不过并没有涉及到异步日志 xff0c 下面
  • muduo异步日志——core dump后查找还未来得及写出的日志

    目录 前言 生成core文件 gdb调试Core文件 前言 通过异步日志的实现可以知道 xff0c 日志消息并不是生成后立刻就会写出 xff0c 而是先存放在前端缓冲区currentBuffer或者前端缓冲区队列buffers中 xff0c
  • C++知识积累:成员函数运算符重载与非成员函数运算符重载

    运算符重载 xff0c 是C 43 43 多态的表现形式之一 xff0c 可以通过对运算符进行重载来实现运算符特定的功能 运算符重载一般具有以下原则 xff1a xff08 1 xff09 不可重载不存在的运算符 xff0c 如重载 来表示
  • (二叉树)二叉树的最近公共祖先

    题目描述 给定一个二叉树 找到该树中两个指定节点的最近公共祖先 百度百科中最近公共祖先的定义为 xff1a 对于有根树 T 的两个结点 p q xff0c 最近公共祖先表示为一个结点 x xff0c 满足 x 是 p q 的祖先且 x 的深
  • 有符号数、无符号数理解

    大家都知道 xff0c 在C C 43 43 中 xff0c 对于w位编译器 xff0c 其有符号数表示的数值范围为 2 w 1 2 w 1 1 xff0c 无符号数表示的数值范围为0 2 w 1 xff0c 举个例子 xff0c 在16位
  • ​PCB的 “ 黑科技 ” ,应该是这个。

    大家好 xff0c 我是张巧龙 xff0c 前段时间炒的很火的折叠屏手机不知道大家还记得不 xff1f 折叠屏手机之所以这么具有 34 韧性 34 xff0c 全靠背后的柔性电路板 FlexiblePrintedCircuit xff0c
  • 指针数组、数组指针——用指针访问数组方法总结

    目录 1 数组元素的访问 2 通过指针访问数组 2 1 通过指针访问一维数组 2 2 通过指针访问二维数组 2 2 1 指向元素的指针 2 2 2 指向每一行的指针 xff08 指针数组方式 xff09 2 2 3 指向整个数组的指针 xf
  • C++知识积累:如何获取虚函数表以及虚函数地址

    如果一个类中存在虚函数的话 xff0c 那么编译器就会为这个类生成一个虚函数表 xff0c 这个虚函数表中按照个虚函数的声明顺序存放了各个虚函数的地址 xff0c 需要注意的是 xff0c 这个虚函数表并不存在于类中 xff0c 而对于这个
  • C++多线程:互斥锁

    目录 1 前言 2 互斥锁 2 1 互斥锁的特点 2 2 互斥锁的使用 2 2 std lock guard 3 死锁 3 1 死锁的含义 3 2 死锁的例子 3 3 死锁的解决方法 1 前言 比如说我们现在以一个list容器来模仿一个消息