
本文共 18046 字,大约阅读时间需要 60 分钟。
互斥量概念、用法、死锁演示及解决详解
1.互斥量(mutex)的基本概念
保护共享号数据,某个线程操作时需要把共享数据锁住,然后操作数据,最后解锁。
其他想操作共享数据的线程必须等待解锁,锁定住,操作,解锁。
互斥量:类对象,理解为一把锁,多个线程尝试用lock函数来加锁这个锁头,只有一个线程能锁定成功,如果没有锁成功,则一直卡在lock函数直到有线程锁定成功
互斥量使用要小心,保护多了或少了都不好。
2.互斥量的用法
2.1 lock()和unlock()
当不使用lock和unlock时,同时读写绘出错
当我们加入lock和unlock时
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; my_mutex.lock(); msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 my_mutex.unlock(); } } bool outMsgLULproc(int &command) { my_mutex.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 my_mutex.unlock(); return true; } else { my_mutex.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex; //创建一个互斥量};int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。}
就可以同时进行读写操作
2.2 std::lock_guard类模板
std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock
示例:
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 my_mutex.unlock(); } } bool outMsgLULproc(int &command) { lock_guard<mutex>sbguard(my_mutex); //sbguard是对象名 /*my_mutex.lock();*/ if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 /*my_mutex.unlock();*/ return true; } else { /*my_mutex.unlock();*/ return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex; //创建一个互斥量};int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock}
工作原理:lock_guard构造函数里执行了lock函数,析构lock_guard时,执行了unlock函数
3 死锁
3.1 死锁演示
死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)
两个线程A,B
线程A执行时,先锁金锁,然后准备去锁银锁,两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁产生了死锁,下面用两个互斥量演示死锁。
代码演示:
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 my_mutex2.lock(); //再锁银锁 msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 my_mutex2.unlock(); //顺序无所谓 my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 my_mutex2.lock(); my_mutex1.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 my_mutex1.unlock(); my_mutex2.unlock(); return true; } else { my_mutex1.unlock(); my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 */ //5.1死锁演示}
3.2 死锁的解决方案
只要保证两个互斥量上锁的顺序一致,就不会造成死锁。
(解锁顺序不影响是否发生死锁)
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 my_mutex2.lock(); //再锁银锁 msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 my_mutex2.unlock(); //顺序无所谓 my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 my_mutex1.lock(); my_mutex2.lock(); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 my_mutex1.unlock(); my_mutex2.unlock(); return true; } else { my_mutex1.unlock(); my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。}
使用lock_guard产生死锁和解决死锁的方法相同。
3.3 std::lock()函数模板
能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行),不存在因为锁头的顺序问题导致出现死锁问题。如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走
特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。
一般用来处理多个互斥量的情况
缺点:需要手动解锁
用一个std:lock()和两个unlock来保护共享数据,示例:
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 my_mutex2.unlock(); //顺序无所谓 my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); std::lock(my_mutex1, my_mutex2); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 my_mutex1.unlock(); my_mutex2.unlock(); return true; } else { my_mutex1.unlock(); my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况}
程序正常运行
但这样使用还存在这需要手动写解代码的问题。
3.4 std:lock_guard的std::adopt_lock参数
为了解决使用了std::lock()还需要手动解锁的问题,使用不需要手写解锁代码的std::lock_guard,但是在std::lock(mutex1,mutex2)中,已经上锁一次,再使用std::lock_guard还会再上锁一次,所以使用std::lock_guard中的adopt_lock参数,可以在不调用上锁函数,保证只锁一次。
示例代码如下:
// 并发与多线程2_4.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。//#include "pch.h"#include <iostream>#include <vector>#include <thread>#include <list>#include <mutex>using namespace std;//vector<int>g_v = { 1,2,3 };////void Myprint(int inum)//{ // cout << "myprint线程开始执行了,线程编号" << inum << endl;// cout << "myprint线程结束执行了,线程编号" << inum << endl;// cout << "id 为" << std::this_thread::get_id() << "打印g_v值" << g_v[0] << g_v[1] << g_v[2] << endl;////}class A{ public: //把玩家命令放入到一个队列的进程 void inMsgRecvQueue() { for (int i = 0; i < 100000; i++) { //my_mutex.lock(); cout << "inMsgRecvQueue执行,插入一个元素" << i << endl; { //my_mutex1.lock(); //先锁金锁 实际中两个lock间会执行其他的东西 //my_mutex2.lock(); //再锁银锁 //使用std::lock() std::lock(my_mutex1, my_mutex2); //相当与每个互斥量都调用了lock std::lock_guard<mutex>sbguard1(my_mutex1, adopt_lock); std::lock_guard<mutex>sbguard2(my_mutex2, adopt_lock); msgRecvQueue.push_back(i); //假设数字i为命令 放入队列 //my_mutex2.unlock(); //顺序无所谓 //my_mutex1.unlock(); } } } bool outMsgLULproc(int &command) { //lock_guard<mutex>sbguard(my_mutex1); //sbguard是对象名 //my_mutex1.lock(); //my_mutex2.lock(); std::lock(my_mutex1, my_mutex2); if (!msgRecvQueue.empty()) { command = msgRecvQueue.front(); //返回第一个元素但不检查元素是否存在 msgRecvQueue.pop_front(); //移除第一个元素但不返回 //处理数据。。。。。 my_mutex1.unlock(); my_mutex2.unlock(); return true; } else { my_mutex1.unlock(); my_mutex2.unlock(); return false; } } //读取命令的线程 void outMsgRecvQueue() { int command = 0; for (int i = 0; i < 100000; i++) { bool result = outMsgLULproc(command); if (result == true) { cout << "outMsgRecvQueue 执行,取出一个元素" << command << endl; //数据处理 } } cout << "end" << endl; }private: std::list<int>msgRecvQueue; mutex my_mutex1; //创建一个互斥量 mutex my_mutex2; };int main(){ // //一 创建线程和等待多个线程 //vector<thread>mythreads; 创建10个线程,线程入口函数统一使用 myprint 1)多线程执行顺序是乱的 2)这种join写法更容易写出稳定程序 3)把thread对象放入到容器里,对管理大量线程有帮助 //for (int i = 0; i < 10; i++) //{ // //创建10个线程,已经开始执行 // mythreads.push_back(thread(Myprint, i)); // //} //for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter) //{ // iter->join(); //} //cout << "I LOVE CHINA" << endl; //二 数据共享 //2.1只读数据 //2.2 有读有写 //最简单的不崩溃处理 读和写不能同时进行 //2.3其他案例 //数据共享: //三 共享数据的保护案例代码 //网络游戏服务器,有两个自己创建的线程,一个线程手机玩家命令(数字表示),并把名利数据写入到一个队列中。 //另一个线程从队列中取出玩家发送来的命令,解析,然后执行玩家的动作。 //使用list,频繁的按顺序插入和删除时效率较高 //用成员函数作为线程函数的方法写线程 A myobja; thread myOutnMsg(&A::outMsgRecvQueue, &myobja); thread myInMsgObj(&A::inMsgRecvQueue, &myobja); myOutnMsg.join(); myInMsgObj.join(); //四 互斥量的概念 //步骤:先lock 操作共享数据 然后unlock //lock和unlock要成对使用。有lock忘记unlock的问题非常难排查 //为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板 //智能指针(unique_ptr<>) //std::lock_guard类模板 直接取代lock()和unlock(),用了类模板不能再用lock和unlock //要将保护量放在lock和unlock里 //五 死锁 /* **死锁问题的前提条件是:有至少两个锁,即至少两个互斥量,金锁(Jinlock),银锁(Yinlock)** 两个线程A,B 线程A执行时,先锁金锁,然后去锁银锁 两个线程出现了上下文切换,线程B执行了,线程B先锁银锁,因为银锁没有被A锁上,所以被B锁上了,然后线程B去锁金锁 此时产生了死锁 线程A锁不了银锁,流程走不下去 线程B锁不了金锁,流程走不下去 死锁产生的关键是两个互斥量的上锁顺序不一致 */ //5.1死锁演示 //5.2死锁的一般解决方案 //只要保证两个互斥量上锁的顺序一致,就不会造成死锁。 //5.3 std::lock()函数模板 //能力:一次锁住两个或者两个以上的互斥量(至少两个,多了不限,一个不行) //不存在因为锁头的顺序问题导致出现死锁问题。 //如果互斥量中有一个没锁住,则等待,等待所有互斥量否锁住,才能继续往下走 //特点:要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没成功,则立即解锁已经锁住的。 //用来处理多个互斥量的情况 //5.4 std:lock_guard的std:adopt_lock参数}
通过std::lock(mutex1,mutex2,…)和std::lock_guard相结合的方式,可以做到上锁时不考虑上锁顺序,同时不需要手写解锁代码。
总结:在需要保护共享数据时,最安全的方法时一个一个上锁,一个一个解锁。
发表评论
最新留言
关于作者
