前言
本节课的主要内容是解决线程中数据共享的问题
提示:以下是本篇文章正文内容,下面案例可供参考
一、数据共享的定义以及示例问题
数据共享的定义:在多个线程中读/写一个变量。
那我们首先要知道:变量能同时读取一个数据但不能同时写和读或者一起写同一个数据。
数据我们可以定义为全局变量或类中的一个成员。
二、解决方案_互斥量、lock()、unlock()
引导
功能为:myin()加数据到list中,myou()输出myin加入的数据
myin()功能为写,myou为读
大家可以试一下自己实现:
1、mutex互斥量
互斥量是上什么:如果不需要信号量的计数能力,有时可以使用信号量的一个简化版本,称为互斥量(mutex)
简单来说就是用来锁住一段代码,其他线程再次锁时就需要等待线程解锁
他在C++中头文件为:#include <mutex>
他是一个类
他的定义为:mutex m;
2、mutex::lock()锁
作用:用来锁住一段代码,其他线程使用时就需要等待解锁。
可以有效的缓解同时读/又读又写的线程
mutex m; m.lock();//如果没有及时解锁,则:锁到程序结束,并且其他人拿不到锁 ........ cout<<"hello world"<<endl;
3、mutex::unlock()解锁
作用:用来解锁一段代码,其他线程可以在次锁这个互斥量。
锁的意义:不让变量同时读写
4、范例演示
讲解:我们有2个线程,他们又读又写,所以我们需要锁住变量以保证数据安全。
我们使用完数据后也需要解锁,要不然其他线程会卡到lock()永远执行不到
class A { public: //功能为:myin()加数据到li中,myou()输出myin加入的数据 void myin() { for (int i = 0; i < 100000; i++) { cout << "插入数据:" << i << endl; m.lock();//加锁保护数据安全 li.push_back(i); m.unlock();//使用完后需要及时解锁 } } void myou() { for (int i = 0; i < 100000; i++) { m.lock();//加锁保护数据安全 if (!li.empty())//不为空时输出数据并删除他 { //输出数据 cout << "数据输出:" << li.front() << endl; li.pop_front(); } else//为空则取不到 cout << "队列为空" << endl; m.unlock();//使用完后需要及时解锁 } } //定义一个list队列 list<int> li; //定义一个互斥量 mutex m; }; int main() { A a; //创建2个线程 thread t(&A::myin); thread th(&A::myou); //让主线程等待 t.join(); th.join(); return 0; }
现在就可以稳定地跑起来了。
三、lock_gurad类模板、死锁及解决方案
1、为什么要使用lock_gurad
我相信,有些人lock()后总会忘记unlock()导致其他线程执行不到,卡在lock()那
怎么办呢?系统/C++给我们提供了一个类模板,使用他就不用unlock()了
2、lock_gurad的使用
他其实就是一个类模板
他的作用是不用unlock锁了,他自动unlock()
改一下刚刚的范例:
void myin() { for (int i = 0; i < 100000; i++) { cout << "插入数据:" << i << endl; lock_guard<mutex> lo(m); li.push_back(i); } } void myou() { for (int i = 0; i < 100000; i++) { lock_guard<mutex> lo(m); if (!li.empty())//不为空时输出数据并删除他 { //输出数据 cout << "数据输出:" << li.front() << endl; li.pop_front(); } else cout << "队列为空" << endl; } }
他还是能正常运行,所以我们写的没有问题
他还有一个功能:变成unlock()
在lock_gurad构造函数参数2写adopt_lock
拓展知识std::lock:一次锁住多个mutex互斥量:
实际:类模板
mutex _1; mutex _2; std::lock<mutex>(_1,_2);
3、死锁情况的演示
大概情况讲解:线程A拿到mu1锁,在拿mu2锁,然后解锁mu2,解锁mu1 线程B呢:拿mu2锁,再拿mu1,然后解锁mu1,解锁mu2。这就导致有可能A,B都不解锁。卡在那:
解决方案:锁和解锁两个线程顺序要一样
四、unique_lock lock_gurad加强版
1、与lock_gurad的对比
为什么说unique_lock是 lock_gurad的加强版呢?
答:unique_lock用法更灵活,更多的参数,更高的效率
2、使用
unique_lock也是一个类模板
基本功能和lock_gurad一样一样的,那他灵活在哪呢?
成员函数,和构造函数的参数1多种多样:
构造函数参数1:
1).try_to_lock
没有拿到锁立马返回,立马unlock
怎么看自己有没有拿到锁呢
使用owns_lock函数
unique_lock<mutex> uni(m); if(uni.owns_lock) { cout<<"拿到了锁"<<endl; //干其他的事情 } else cout<<"没有拿到锁"
2).defer_lock
初始化一个mutex
前提:此mutex没有加锁
成员函数:
1).lock()
手动lock()
2).unlock()
手动unlock()
unique_lock可以自己加解锁
3).release()放弃mutex的所以权,并返回mutex的指针
所有权指的是:放弃他的操作,简单的说:调用了release函数就不能操作那个绑定的mutex