C++并发与多线程---学习笔记(2)多线程创建、数据共享问题处理、死锁演示及解决详解
2021/8/5 1:06:49
本文主要是介绍C++并发与多线程---学习笔记(2)多线程创建、数据共享问题处理、死锁演示及解决详解,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
C++11并发与多线程
- 一、创建多个线程、数据共享问题分析、案例代码
- (1)创建和等待多线程
- (2)数据共享问题分析
- 1)只读数据
- 2)有读有写
- (3)共享数据读写案例崩溃案例演示,且抛出“互斥量”
- 二、互斥量概念、用法、死锁演示及解决详解
- (1)互斥量的基本概念
- (2)互斥量用法
- (3)死锁
- (4)死锁解决方法
一、创建多个线程、数据共享问题分析、案例代码
(1)创建和等待多线程
创建10个线程,线程入口函数统一使用myprint,注意事项:
- a)多个线程执行顺序是乱的,跟操作系统内部对线程的运行调度机制有关
- b)主线程等待所有子线程运行结束后,最后主线程结束,推荐使用join()写法,更容易写出稳定的程序
- c)把thread对象放入容器里管理,弄成thread对象数组,这对我们一次创建大量的线程并对大量线程管理更方便
/*子函数*/ void myprint(const int&i) { cout << "myprint线程开始编号:" << i << endl; //..... cout << "myprint线程结束编号:" << i << endl; } /*主函数*/ vector<thread>mythread; for (int i = 0; i < 10; i++) { mythread.push_back(thread(myprint,i));//匿名对象创建,创建10个线程且同时开始进行 } for (auto&iter :mythread) { iter.join(); } //for (auto iter = mythread.begin(); iter != mythread.end(); iter++) { // iter->join(); //} //
(2)数据共享问题分析
1)只读数据
注意事项:只读数据是安全稳定的,不需要特别什么处理手段,直接读就可以
/*子函数*/ vector<int>number = {1,2,3};//数据共享 void dataprint(const int&i) { cout << "dataprint的id:" << this_thread::get_id() << "number数据:" << number[0] << number[1] << number[2] << endl;; } /*主函数*/ vector<thread>mythread; for (int i = 0; i < 10; i++) { mythread.push_back(thread(dataprint, i)); } for (auto&iter : mythread) { iter.join(); }
2)有读有写
注意事项:
- 假设有两个线程写,八个线程读,如果代码没有特别的处理,那程序肯定会崩溃的,因此最简单的处理方式是读的时候不写,写的时候不写,两个写的线程不能同时写,八个读的线程不能同时读。
(3)共享数据读写案例崩溃案例演示,且抛出“互斥量”
假设:
网络游戏服务器,两个自己创建的线程,一个线程手机玩家命令,另一个线程取出玩家送来的命令
(用成员函数作为线程函数方法写线程)
#include<iostream> #include<thread> #include<string> #include<vector> #include<list> using namespace std; class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { cout << "服务器收集数据数量:" << i << endl; num.push_back(i); } } void messageOut() { for (int i = 0; i < 10000; i++) { if (!num.empty()) { int comment = num.front(); num.pop_front(); cout << "服务器已发送的数据数量" << i << endl; } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; }; int main() { Example ex; thread out(&Example::messageOut,&ex);//第二个参数是 引用,保证线程里用的是同一个对象ex,(防止拷贝新的对象) thread in(&Example::messageIn,&ex); out.join(); in.join(); cout << "主线程运行" << endl; system("pause"); return 0; }
- 该代码运行时会出现崩溃因两个线程有时会同时运行
- 因此代码的正常运行就要两线程有序的进行,不能你争我抢,这时锁头(互斥量)就可以起到很好的作用!
- 保护共享数据,操作时,某各个线程用代码把共享数据锁住、操作数据、解锁,其他想操作共享数据的线程必须等待解锁、锁定住、操作、解锁。
二、互斥量概念、用法、死锁演示及解决详解
(1)互斥量的基本概念
互斥量是个类对象 理解成一把锁,多个线程尝试用lock()成员函数来枷锁这把锁头,只有一个线程能锁定成功(成功的标志是能返回)。
注意事项:
- 如果没锁成功,那么流程卡在lock()这里不断的尝试去锁这把锁头
- 互斥量使用要小心,保护数据不多也不少,少了,没达到保护效果,多了,影响效率
(2)互斥量用法
- lock()函数-----用锁
- unlock()函数----解锁
注意事项:
- 先lock(),操作共享数据,后unlock()
- lock()和unlock()要成对使用,有lock必然要有unlock,要有对称性,缺一不可!
class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { myMutex.lock(); cout << "服务器收集数据数量:" << i << endl; num.push_back(i); myMutex.unlock(); } } void messageOut() { for (int i = 0; i < 10000; i++) { if (!num.empty()) { myMutex.lock(); int comment = num.front(); num.pop_front(); cout << "服务器已发送的数据数量" << i << endl; myMutex.unlock(); } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; mutex myMutex; };
深入与提升:
-
为了防止忘记unlock(),引入std::lock_guard的类模板,你忘记unlock,模板会自动unlock
//相当 于智能指针一样(unique_ptr<>):您忘记释放内存,自动给你释放
-
std::lock_guard类模板:直接取代lock和unlock,用了类模板就不要用lock和unlock
class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { //myMutex.lock(); std::lock_guard<mutex>guard(myMutex); cout << "服务器收集数据数量:" << i << endl; num.push_back(i); // myMutex.unlock(); } } void messageOut() { for (int i = 0; i < 10000; i++) { if (!num.empty()) { //myMutex.lock(); std::lock_guard<mutex>guard(myMutex); int comment = num.front(); num.pop_front(); cout << "服务器已发送的数据数量" << i << endl; //myMutex.unlock(); } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; mutex myMutex; };
- std::lock_guard类模板实际是在lock_guard构造函数执行了lock(),析构函数执行unlock()
(3)死锁
死锁是至少两个锁头(两个互斥量)才能产生
说明:
- 线程A执行的时候,这个线程先锁金锁,把金锁lock成功了,然后去lock 银锁,出现上下文切换,线程b执行的时候,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会lock成功,接着线程b去锁金锁,此时就出现死锁
- 线程a因为拿不到银锁头,流程走不下去(所有后边代码有解锁金锁头的但是流程走不下去,所以金锁头解不开)
- 线程b因为拿不到金锁头,流程走不下去(所有后边代码有解锁银锁头的但是流程走不下去,所以银锁头解不开)
代码示例:
class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { myMutex1.lock(); myMutex2.lock(); cout << "服务器收集数据数量:" << i << endl; num.push_back(i); myMutex2.unlock(); myMutex1.unlock(); } } void messageOut() { for (int i = 0; i < 10000; i++) { if (!num.empty()) { myMutex2.lock(); myMutex1.lock(); int comment = num.front(); num.pop_front(); cout << "服务器已发送的数据数量" << i << endl; myMutex2.unlock(); myMutex1.unlock(); } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; mutex myMutex1; mutex myMutex2; };
(4)死锁解决方法
- 只要保证两个互斥量上锁的顺序一致就不会死锁
class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { myMutex1.lock(); myMutex2.lock(); cout << "服务器收集数据数量:" << i << endl; num.push_back(i); myMutex2.unlock(); myMutex1.unlock(); } } void messageOut() { for (int i = 0; i < 10000; i++) { if (!num.empty()) { myMutex1.lock(); myMutex2.lock(); int comment = num.front(); num.pop_front(); cout << "服务器已发送的数据数量" << i << endl; myMutex2.unlock(); myMutex1.unlock(); } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; mutex myMutex1; mutex myMutex2; };
2.std::lock()函数模板:可用来处理多个互斥量
- 一次锁住两个或者两个以上的互斥量(至少两个,多了不限,1个不行),不存在这种因为在多个线程中 锁的顺序问题导致死锁的问题
- 如果互斥量中有一个没锁住,他就在哪里等着,等所有互斥量都锁住,他才能往下走(返回)
- 要么两个互斥量都锁住,要么两个互斥量都没有锁住,如果之前锁了一个另外一个没锁成功则它立即把已经锁住的解锁
class Example { public: void messageIn() { for (int i = 0; i < 10000; i++) { cout << "messageIn(),服务器收集数据数量:" << i << endl; lock(myMutex1, myMutex2); num.push_back(i); myMutex2.unlock(); myMutex1.unlock(); } return; } bool outMessage() { lock(myMutex1, myMutex2); if (!num.empty()) { num.pop_front(); myMutex2.unlock(); myMutex1.unlock(); return true; } myMutex2.unlock(); myMutex1.unlock(); return false; } void messageOut() { for (int i = 0; i < 10000; i++) { bool result = outMessage(); if (result==true) { cout << "messageOut()执行,服务器已发送的数据数量" << endl; } else { cout << "messageOut()执行,但目前消息队列中为空" << i << endl; } } cout << "end" << endl; } private: list<int>num; mutex myMutex1; mutex myMutex2; };
深入与提升:
-
std::lock_guard的adopt_lock参数
dopt_lock是个结构体对象,起一个标志作用:表示这个互斥量已经lock 不需要在std::lock_guard<mutex>构造函数里再对mutex对象进行lock 且std::lock_guard也会自动解锁
这篇关于C++并发与多线程---学习笔记(2)多线程创建、数据共享问题处理、死锁演示及解决详解的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-26怎么使用nsenter命令进入容器?-icode9专业技术文章分享
- 2024-12-26导入文件提示存在乱码,请确定使用的是UTF-8编码怎么解决?-icode9专业技术文章分享
- 2024-12-26csv文件怎么设置编码?-icode9专业技术文章分享
- 2024-12-25TypeScript基础知识详解
- 2024-12-25安卓NDK 是什么?-icode9专业技术文章分享
- 2024-12-25caddy 可以定义日志到 文件吗?-icode9专业技术文章分享
- 2024-12-25wordfence如何设置密码规则?-icode9专业技术文章分享
- 2024-12-25有哪些方法可以实现 DLL 文件路径的管理?-icode9专业技术文章分享
- 2024-12-25错误信息 "At least one element in the source array could not be cast down to the destination array-icode9专业技术文章分享
- 2024-12-25'flutter' 不是内部或外部命令,也不是可运行的程序 或批处理文件。错误信息提示什么意思?-icode9专业技术文章分享