一、互斥量(mutex)的基本概念
1.我们为什么需要互斥量
这就涉及到了共享资源的概念,例如:买票这一行为,(票就是我们的共享资源)
假设有两个窗口 1 和 2号窗口(可以看成是两个"售票"子线程),
这时有两人同时购买从上海到北京的票,同样挑中了A1座,如果这时不对这类
行为做任何处理,就会造成两个人同时买到A1票的错误反馈。
保护共享数据,操作时,某个线程 用代码把共享数据锁住->操作数据->解锁;其他想操作共享数据的线程必须等待解锁->锁定住->操作->解锁; 以上种种引出 “互斥量”
2.互斥量(mutex)的基本概念
- 互斥量是个类对象。可以理解成一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回)
- 如果没有锁成功,那么流程卡在lock()这里不断的尝试去锁这把锁头;
- 互斥量使用要小心,保护数据不多也不少,少了,没达到保护效果,多了影响效率;
二、互斥量的用法
2.1 lock(),unlock()
mutex互斥量是一个类,这个类有一个lock()方法,和一个unlock()方法(成员函数)。
(1)引入头文件
#include <mutex>
using namespace std;
(2)lock()和unlock()
lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面 3 种情况:(1). 如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住,直到调用 unlock之前,该线程一直拥有该锁。(2). 如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。(3). 如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)
步骤:1.先lock();
2.操作共享数据
3.unlock();
lock()和unlock()要成对使用,有lock()必然要有unlock(),
每调用一次lock(),必然应该调用一次unlock();
不应该也不允许调用1次lock()却调用了2次unlock(),也不允许
调用2次lock却调用1次unlock,这些非对称数量的调用都会导致
代码不稳定甚至崩溃。
(3)测试代码
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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();
}
return;
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!msgRecvQueue.empty())
{
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 results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
(4)运行结果
(5)分析总结
- 有lock,忘记unlock的问题,非常难排查;
- 为了防止忘记unlock(),引入了一个叫std::lock_guard的类模板;忘记了加unlock,此类模板会帮我们unlock
- 就像智能指针(unique_ptr()):忘记释放内存,会帮我们释放(保姆)
2.2 std::lock_quard类模板
std::lock_guard类模板:直接取代lock()和unlock(); 即使用了lock_guard后,再也不能使用lock()和unlock()了
(1)测试代码
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
using namespace std;
class A {
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
{
std::lock_guard<std::mutex> sbguard(my_mutex);
msgRecvQueue.push_back(i);
}
}
return;
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> sbguard(my_mutex);
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
(2)运行结果图
(3)总结
三、死锁
例如: 生活中 张三:站在北京等李四来才走,但是一个人时不动 李四:站在上海等张三来才走,但是一个人时不动 这就造成了互相等待,互相不行动 在C++中 假设现在有两把锁(死锁这个问题 是由至少两个“锁头”也就是两个互斥量才能产生);金锁(JinLock),银锁(YinLock); 两个线程A,B (1)线程A执行的时候,这个线程先锁 金锁,把金锁lock()成功了,然后它去lock银锁。。。 这时出现了上下文切换 (2)(切换到了线程B)线程B执行了,这个线程先锁 银锁,因为银锁还没有被锁,所以银锁会lock()成功,接着线程B要去lock金锁… 此时此刻,死锁就产生了; (3)线程A因为拿不到银锁头,流程走不下去(所有后边代码有unlock金锁头的但是流程走不下去,所以金锁头解不开); (4)线程B因为拿不到金锁头,流程走不下去(所有后边代码有unlock银锁头的但是流程走不下去,所以银锁头解不开); 这样两个线程都只能停在这里,A等B,B等A; 这就是死锁。
3.1 死锁演示
(1)代码演示
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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_mutex2.unlock();
my_mutex1.unlock();
}
return;
}
bool outMsgLULProc(int &command)
{
my_mutex2.lock();
my_mutex1.lock();
if (!msgRecvQueue.empty())
{
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 results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
(2)运行结果图
unlock的顺序不重要
3.2 死锁的一般解决方案
(1) 只要保证这两个互斥量上锁的顺序一致就不会死锁【代码+运行结果】
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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_mutex2.unlock();
my_mutex1.unlock();
}
return;
}
bool outMsgLULProc(int &command)
{
my_mutex1.lock();
my_mutex2.lock();
if (!msgRecvQueue.empty())
{
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 results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
3.3 std::lock()函数模板
std::lock()函数模板:用来处理多个互斥量 功能:(同时)一次锁住两个或者两个以上的互斥量(至少两个,多了不行,1个也不行); 它不存在这种在多个线程中 因为锁的顺序问题导致死锁的风险问题; std::lock():如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回); 要么两个互斥量都锁住,要么两个互斥量都没锁住。如果只锁了一个,另外一个没锁成功,则它立即把已经锁住的解锁。 保证不会出现死锁,不管锁的顺序。
(1)代码测试
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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_mutex2.unlock();
my_mutex1.unlock();
}
return;
}
bool outMsgLULProc(int &command)
{
std::lock(my_mutex1, my_mutex2);
if (!msgRecvQueue.empty())
{
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 results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
3.4 std::lcok_guard的std::adopt_lock参数
std::adopt_lock是个结构体对象,起一个标记作用: 作用就是表示这个互斥量已经lock()了,不需要在std::lock_guard< std::mutex >构造函数里面再对mutex对象进行lock()了; std::lock_guard< std::mutex >析构时功能是正常的。
(1)代码测试
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
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> sqm1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sqm2(my_mutex2, std::adopt_lock);
msgRecvQueue.push_back(i);
}
return;
}
bool outMsgLULProc(int &command)
{
std::lock(my_mutex1, my_mutex2);
std::lock_guard<std::mutex> sqm1(my_mutex1, std::adopt_lock);
std::lock_guard<std::mutex> sqm2(my_mutex2, std::adopt_lock);
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool results = outMsgLULProc(command);
if (results == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
}
else
{
cout << "outMsgQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
std::list<int> msgRecvQueue;
std::mutex my_mutex1;
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutnMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutnMsgObj.join();
myInMsgObj.join();
return 0;
}
(2)运行结果
3.5 总结
- std::lock():一次锁定多个互斥量;谨慎使用(建议一个一个锁,因为一般,多个互斥量有多个要保护的东西(保护不同的东西),很少会挨在一起,同时锁定)
|