一、互斥量(mutex)概念的基本概念
- 互斥量是一个类对象,理解成一把锁,多个线程尝试用lock()成员函数来尝试加锁头,只有一个线程能够锁定成功,成功的标志是返回,如果没有所成功,那么这个线程的执行流程就会卡在lock()这里不断尝试去锁这把锁;
- 互斥量使用需要小心:只保护需要保护的数据,也必须保护全(保护多了影响效率,保护少了达不到保护效果),操作完以后要把锁解开,别人才能使用lock继续执行;
二、互斥量的用法
1.lock(),unlock()
- 步骤:先lock(),操作共享数据,再unlock();
- lock()和unlock()要成对使用,每调用一次lock(),必须调用一次unlock();
- 有lock()忘记unlock()一般很难排查;
- 为了防止大家忘记unlock(),引入了一个叫std::lock_guard()的类模板;你忘记unlock(),我帮你unlock();
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i);
my_mutex.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex.unlock();
return true;
}
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()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
2.std::lock_guard类模板
- std::lock_guard类模板:直接取代lock()和unlock();也就是说,使用了lock_guard()后就不能再使用lock()和unlock();
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i);
my_mutex.unlock();
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
三、死锁
- 现实生活中的死锁:张三站在北京等李四,不动,李四站在深圳等张三,不动;
- c++中的死锁(至少有两把锁):比如有两把锁,锁一,锁二,两个线程,线程A,线程B;(业务:需要把两把锁都锁上)
- (1)线程A执行的时候,这个线程先锁锁一;然后去锁锁二;
- (2)出现了上下文切换,线程A被切换走了,线程B开始执行,这个线程先锁锁二成功;然后线程B去锁锁一;
- 此时此刻,死锁就发生了
- (3)线程A锁不了锁二,流程走不下去,所以锁一解不开;
- (4)线程B锁不了锁一,流程走不下去,所以锁二解不开;
1.死锁演示
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex1.lock();
my_mutex2.lock();
msgRecvQueue.push_back(i);
my_mutex1.unlock();
my_mutex2.unlock();
}
}
bool outMsgLULProc(int& command)
{
my_mutex2.lock();
my_mutex1.lock();
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
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()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
2.死锁的一般解决方案
- 死锁产生的原因:锁的顺序不一样,只要保证两个互斥量上锁的顺序一样,就不会产生死锁;
- 使用lock_guard()也是相同的;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock_guard<std::mutex> sbguard1(my_mutex1);
std::lock_guard<std::mutex> sbguard2(my_mutex2);
msgRecvQueue.push_back(i);
;
}
}
bool outMsgLULProc(int& command)
{
std::lock_guard<std::mutex> sbguard1(my_mutex1);
std::lock_guard<std::mutex> sbguard2(my_mutex2);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
3.std::lock()函数模板
-用于处理多个互斥量; 一次锁住两个或者两个以上的互斥量(至少两个);(同时锁住多个互斥量的情况比较少见);
- 不存在在多线程中,因为锁的顺序导致死锁的风险问题;
- std::lock():如果互斥量中有一个每锁住,他就会释放自己锁住的,然后就等在那里,等所有互斥量都锁住,才往下走;
- 要么两个互斥量都锁住或者两个互斥量都释放;如果只锁一个,另外一个没成功,则立即释放锁住的;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex1, my_mutex2);
msgRecvQueue.push_back(i);
my_mutex1.unlock();
my_mutex2.unlock();
}
}
bool outMsgLULProc(int& command)
{
std::lock(my_mutex1, my_mutex2);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex1.unlock();
my_mutex2.unlock();
return true;
}
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()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
4.std::lock_guard的std::adopt_lock参数
- std::adopt_lock 可以使std::lock_guard()在构造函数不进行lock();
- std::adopt_lock是一个结构体对象,起一个标记作用,表示互斥量已经进行lock了,不需要在构造函数里面在对其进行lock;
#include <thread>
#include <mutex>
#include <iostream>
#include <list>
#include <windows.h>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);
}
}
bool outMsgLULProc(int& command)
{
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> sbguard1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sbguard2(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,插入一个元素" << i << endl;
}
else
{
cout << "outMsgRecvQueue()执行,队列为空!" << endl;
}
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
thread myOutMsgobj(&A::outMsgRecvQueue, &myobja);
thread myInMsgobj(&A::inMsgRecvQueue, &myobja);
myOutMsgobj.join();
myInMsgobj.join();
Sleep(10);
return 0;
}
- 建议一个一个的锁,尽量不要同时lock();同时lock()也不常见;
|