【C++】多线程互斥锁、条件变量

news/2024/7/8 7:26:43

我们了解互斥量和条件变量之前,我们先来看一下为什么要有互斥量条件变量这两个东西,了解为什么有这两东西之后,理解起来后面的东西就简单很多了!!!

先来看下面这段简单的代码:

int g_num = 0;void print(int id)
{for (int i = 0; i < 5; i++){++g_num;cout << "id = " << id << "==>" << g_num << endl;std::this_thread::sleep_for(std::chrono::seconds(1));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}

上述代码功能大致就是在线程tha和thb中运行函数print,每个线程对g_num进行加加一次,最后加出来的g_num的值应该是10,那么我们现在来看结果:

我们看到运行结果,为什么打印结果最后,按理来说两个线程各加五次,最后结果应该是10呀,怎么会是9呢?

 如上图所示,是因为++这个运算符不是原子操作(不会被线程调度机制打断的操作),我们可以将g_num设置为原子数,改为atomic_int g_num = 0;

atomic_int g_num = 0; //将g_num设置为原子操作数
//atomic<int> g_num = 0;这个和上面是一样的 下面这行是模板化之后的void print(int id)
{for (int i = 0; i < 5; i++){++g_num;cout << "id = " << id << "==>" << g_num << endl;std::this_thread::sleep_for(std::chrono::seconds(1));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}

将g_num设置为原子操作数之后,在++阶段就不会被线程调度机制给打断,我们来看运行结果:

运行结果是我所期望的但是中间那块又出了一点小状况连着打着两个4,两个6,这种情况该怎么办呢?

下面就该说道我们的互斥锁了:

互斥锁:

        在上述代码中我们使用了共享资源----->全局量g_num,两个线程同时对g_num进行++操作,为了保护共享资源,在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )解锁( unlock )

具体的互斥锁概念在我另外一篇博客中讲过Linux中互斥锁和条件变量的概念:Linux多线程的同步-----信号量和互斥锁_神厨小福贵!的博客-CSDN博客前面两篇给基本概念讲过了,大家有兴趣的可以去看一下:Linux多线程_神厨小福贵!的博客-CSDN博客进程和线程的区别有哪些呢?进程是资源分配的最小单位,线程是CPU调度的最小单位进程有自己的独立地址空间,线程共享进程中的地址空间进程的创建消耗资源大,线程的创建相对较小进程的切换开销大,线程的切换开销相对较小进程:程序执行的过程叫进程。线程:进程内部的一条执行序列或执行路径,一个进程可以包含多条线程(多线程)!每个进程最少有一个线程,例如下面代码:#include <stdio.h>int mahttps://blog.csdn.net/qq_45829112/article/details/121524904

来看下面代码:

int g_num = 0;
std::mutex mtx;  //创建锁对象void print(int id)
{for (int i = 0; i < 5; i++){mtx.lock();  //上锁++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock(); //解锁std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}

我们来看运行结果:符合我们最初的预期。

 打开官方文档,可以看到

创建锁对象只有这个方法,拷贝构造被删除了 。

std::mutex::try_lock

        对于互斥锁的lock和unlock我们都很熟悉了,下面来说一下std::mutex::try_lock这个成员函数!

 try_lock字面意思就是说尝试上锁,如果上锁成功,返回true,上锁失败则返回false,但是如果上锁失败,他还是会接着往下运行,不会像lock哪运被阻塞在上锁那块,所以try_lock必须得在循环中使用:

int g_num = 0;
std::mutex mtx;void print(int id)
{for (int i = 0; i < 5; i++){mtx.try_lock();  //代码只是将lock换成了try_lock且没把try_lock扔在循环中执行++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}

我们来看运行结果:

 unlock of  unowned  mutex,这玩意思就是说你在给个没上锁的互斥锁解锁,所以报这错误,因此try_lock搁在普通语句中,会有很大的问题,现命我们演示一下将这玩意放到循环中去弄一边:

int g_num = 0;
std::mutex mtx;void print(int id)
{for (int i = 0; i < 5; i++){while (!mtx.try_lock())  //try_lock失败时为false 前面加了!,所以失败时为true 然后打印尝试加锁{                    //然后再次尝试加锁,只有加锁成功了,才能出这个while循环cout << "try  lock" << endl;}	++g_num;cout << "id = " << id << "==>" << g_num << endl;mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(500));}
}int main()
{thread tha(print, 0);thread thb(print, 1);tha.join();thb.join();return 0;
}

我们来看运行结果:

 运行结果符合我们的预期,但是try_lock这个函数有个不好处是太损耗资源了,当它加锁失败时,一直尝试加锁一直尝试加锁,损耗CPU资源。

条件变量:condition_variable

        

框住这三个函数较为重要,下面着重来说下面这三个函数: 这里顺便说一下,下面代码将会是条件变量和互斥锁的结合使用,至于为什么要将互斥锁和条件变量一起使用,原因就是互斥锁状态太单一了,而条件变量允许阻塞,接收信号量等刚好弥补了互斥锁的缺陷所以这些一起使用!!!

        这三个函数呢,通过一个小实验来实现,通过多线程分别打印123一直到100:

std::mutex mtx;
std::condition_variable cv;
int isReady = 0;
const int n = 100;void print_A()
{std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlockint i = 0;while (i < n){while (isReady != 0) {cv.wait(lock);//互斥锁和信号量一起使用  wait参数为锁对象}cout << "A" ;isReady = 1;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all(); //当isReady等于0时print_B 和 print_C 处于阻塞状态  //该函数就是唤醒所有等待的函数,然后通过isReady来进行判断要进行那个函数的运行}
}void print_B()
{std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlockint i = 0;while (i < n){while (isReady != 1){cv.wait(lock);}cout << "B" ;isReady = 2;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all();}
}void print_C()
{std::unique_lock<mutex> lock(mtx);  //unique_lock相当于线程中的智能制造 自动解锁,不需要再unlockint i = 0;while (i < n){while (isReady != 2){cv.wait(lock);}cout << "C" ;isReady = 0;++i;std::this_thread::sleep_for(std::chrono::microseconds(100));cv.notify_all();}
}int main()
{thread tha(print_A);thread thb(print_B);thread thc(print_C);tha.join();thb.join();thc.join();return 0;
}

上面代码解析:

运行结果:

我们可以看到上述代码最后唤醒其他线程使用的是notify_all()函数,notify_all()函数作用就是环球其他阻塞的函数,然后因为isready这个数的存在,所以就会选择合适的线程来进行执行,如果我们使用notify_one()呢,先来说下notify_one()函数的作用是什么。notify_one()函数是唤醒其他线程(随机唤醒,这道题中不适合,因为如果打印完A之后唤醒了C线程那么就会一直阻塞在那块)

我们试一下notify_one()函数,可以发现这玩意确实会堵塞在那块:

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

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

相关文章

【C++】二叉树的先序、中序、后序遍历序列

二叉树常用到的遍历有这三种 先序遍历&#xff1a;先遍历根节点&#xff0c;然后再分别遍历左节点和右节点。(根左右) 中序遍历&#xff1a;先遍历左节点&#xff0c;然后再遍历根节点&#xff0c;最后遍历右节点。(左根右) 后序遍历&#xff1a;先遍历左节点&#xff0c;然…

软件工程需求设计说明书

Java即时通聊天程序 设计需求说明书 专业班级&#xff1a; 计本班1202班 项目组成员&#xff1a; 杨宗坤 刘瑞 满亚洲 指导教师&#xff1a; 张利峰 开始日期&#xff1a; 完成日期&#xff1a; 编写目的&#xff1a; 本说明书是在充分理解系统需求分析…

【C++】菱形继承

我们先来看下菱形继承的基本视图以及基本的代码结构 下面来看下简单的代码以及数据结构&#xff1a; class Person { public:int a_p; };class Studen :public Person { public:int a_st; };class Stuff :public Person { public:int a_sf; };class st_sf :public Stuff, publ…

ie旋转滤镜Matrix

旋转一个元素算是一个比较常见的需求了吧&#xff0c;在支持CSS3的浏览器中可以使用transform很容易地实现&#xff0c;这里有介绍&#xff1a;http://www.css88.com/archives/2168&#xff0c;这里有演示http://www.css88.com/tool/css3Preview/Transform.html&#xff0c;就不…

【C++】四种类型的转换

C四种类型的转换 包括这四种&#xff1a;const_cast , static_cast , dynamic_cast , reinterpret_cast 先来说下C语言中的类型转换&#xff0c;非常的暴力&#xff0c;就是耍流氓&#xff1a; float a 12.23; int b (int)a; 下面我写的都是最基础的&#xff0c;简单的&am…

【C++】满二叉树、完全二叉树等概念解释

二叉树中的判断有以下几种&#xff1a; 是否完全二叉树、是否满二叉树、是否为BST树、是否为平衡二叉树、是否为对称二叉树、完美二叉树 满二叉树&#xff1a; 除最后一层无任何子节点外&#xff0c;每一层上的所有结点都有两个子结点的二叉树。 上述所示图除最外一层节点之外…

【C++】多线程(链式、循环队列)实现生产者消费者模式

生产者消费者模式&#xff1a; 生产者消费者问题&#xff08;英语&#xff1a;Producer-consumer problem&#xff09;&#xff0c;也称有限缓冲问题&#xff08;英语&#xff1a;Bounded-buffer problem&#xff09;&#xff0c;是一个多线程同步问题的经典案例。该问题描述了…

添加引用方式抛出和捕获干净的WebService异常

转载&#xff1a;http://www.cnblogs.com/ahdung/p/3953431.html 说明&#xff1a;【干净】指的是客户端在捕获WebService&#xff08;下称WS&#xff09;抛出的异常时&#xff0c;得到的ex.Message就是WS方法中抛出的异常消息&#xff0c;不含任何“杂质”。 前提&#xff1a;…