第十二节 windows临界区、其他各种mutex互斥量
一和二、windows临界区
- 在“同一个线程中”(不同线程就会卡主等待),windows中的“相同临界区变量”代表的临界区的进入(EnterCriticalSection)可以被多次调用。
- Windows临界区,“同一个线程‘”是可以重复进入的,但是进入的次数与离开的次数必须相等,而不会引起程序异常报错。
- C++互斥量mutex则不允许同一个线程重复加锁,会报异常。
- windows临界区是windows编程中的内容,了解一下即可,效果几乎可以等同于c++11的mutex,可以保护一个代码段。
#include <iostream>
#include <thread>
#include <list>
#include <mutex>
#include <Windows.h>//包含头文件
#define __WINDOWSJQ_//定义一个开关
using namespace std;
class A
{
public:
// 把收到的消息传入队列
void inMsgRecvQueue()
{
for (size_t i = 0; i < 1000; ++i)
{
cout << "收到消息,并放入队列 " << i << endl;
#ifdef __WINDOWSJQ_
EnterCriticalSection(&my_winsec); // 进入临界区,类似lock加锁
//EnterCriticalSection(&my_winsec); // 可以再次进入临界区,程序不会出错
msgRecvQueue.push_back(i);
LeaveCriticalSection(&my_winsec); // 离开临界区,类似unlock解锁
//LeaveCriticalSection(&my_winsec); // 如果进入两次,必须离开两次不会报错
#else
my_mutex.lock();
msgRecvQueue.push_back(i);//假设这个数字是我收到的命令,我直接弄到消息队列里来
my_mutex.unlock();
#endif // __WINDOWSJQ_
}
cout << "消息入队结束" << endl;
}
// 从队列中取出消息
void outMsgRecvQueue()
{
for (size_t i = 0; i < 1000; ++i)
{
#ifdef __WINDOWSJQ_
EnterCriticalSection(&my_winsec); // 进入临界区
if (!msgRecvQueue.empty())
{
// 队列不为空
int num = msgRecvQueue.front();
cout << "从消息队列中取出 " << num << endl;
msgRecvQueue.pop_front();
}
else
{
// 消息队列为空
cout << "消息队列为空 " << endl;
}
LeaveCriticalSection(&my_winsec); // 离开临界区
#else
my_mutex.lock();
if (!msgRecvQueue.empty())
{
// 队列不为空
int num = msgRecvQueue.front();
cout << "从消息队列中取出 " << num << endl;
msgRecvQueue.pop_front();
my_mutex.unlock();
}
else
{
// 消息队列为空
cout << "消息队列为空 " << endl;
my_mutex.unlock();
}
#endif // __WINDOWSJQ_
}
cout << "消息出队结束" << endl;
}
A()//类A的构造函数
{
#ifdef __WINDOWSJQ_
InitializeCriticalSection(&my_winsec); //用windows的临界区之前要初始化,而mutex不用
#endif // __WINDOWSJQ_
}
private:
list<int> msgRecvQueue;//容器,用来代表玩家发送来的命令
std::mutex my_mutex;//创建互斥量
#ifdef __WINDOWSJQ_
CRITICAL_SECTION my_winsec; //定义windows中的临界区,非常类似C++11中的mutex
#endif // __WINDOWSJQ_
};
int main()
{
A myobj;
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);
myInMsgObj.join();
myOutMsgObj.join();
return 0;
}
三、自动析构技术
- C++11:lock_guard防止忘了释放信号量,是在析构时自动释放。
- windows:可以写个类,用于自动释放临界区,防止忘记LeaveCriticalSection导致死锁情况的发生
//写个类
class CWinLock {
public:
CWinLock(CRITICAL_SECTION *pCritmp)//构造函数
{
my_winsec = pCritmp;
EnterCriticalSection(my_winsec);
}
~CWinLock()//析构函数
{
LeaveCriticalSection(my_winsec)
};
private:
CRITICAL_SECTION *my_winsec;
};
//main里这样用
#ifdef __WINDOWSJQ_
CWinLock winlock(&my_winsec);
CWinLock winlock2(&my_winsec);//多次调用也没问题
msgRecvQueue.push_back(i);
- 上述这种CWinLock类叫RAII类(Resource Acquisition is initialization),即资源获取及初始化(构造函数初始化一个东西,析构函数中释放一个东西)。容器,智能指针都属于这种类。
四、递归的独占互斥量 std::recursive_mutex
- std::mutex 独占式互斥量,自己lock时别人lock不了
- std::recursive_mutex:递归的独占互斥量,有lock也有unlock,它允许在同一个线程中同一个互斥量多次被lock() 。
- 但是递归加锁的次数是有限制的,太多可能会报异常,效率要比mutex低。
- 如果你真的用了recursive_mutex要考虑代码是否有优化空间,如果能调用一次lock()就不要调用多次。
五、带超时的互斥量 std::timed_mutex 和 std::recursive_timed_mutex
5.1 std::timed_mutex:是带超时功能的独占互斥量
- timed_mutex有mutex的功能且多了以下两个函数。
- try_lock_for():等待一段时间,如果拿到了锁,或者超时了未拿到锁,就继续执行(有选择执行)如下:
//在inMsgRecvQueue函数里
void inMsgRecvQueue()
{
for (size_t i = 0; i < 1000; ++i)
{
cout << "收到消息,并放入队列 " << i << endl;
std::chrono::milliseconds timeout(100);
if (my_mutex.try_lock_for(timeout)) {//等待100ms尝试获取锁
//在100ms内拿到了锁
msgRecvQueue.push_back(i);
my_mutex.unlock();//用完了解锁
}
else {//100ms内没拿到锁
std::chrono::milliseconds sleeptime(100);
std::this_thread::sleep_for(sleeptime);
}
}
cout << "消息入队结束" << endl;
}
private:
list<int> msgRecvQueue;
std::timed_mutex my_mutex;//类中定义带超时功能的独占互斥量
- try_lock_until():参数是一个未来的时间点,在这个未来的时间没到的时间内,如果拿到了锁头,流程就走下来,如果时间到了没拿到锁,流程也可以走下来。
//在inMsgRecvQueue函数里
std::chrono::milliseconds timeout(100);
if (my_mymutex.try_lock_until(chrono::steady_clock::now() + timeout)){//当前时间点+100ms
//在未来的100ms内拿到了锁
msgRecvQueue.push_back(i);
my_mutex.unlock();//用完了解锁
}
else{
std::chrono::milliseconds sleeptime(100);
std::this_thread::sleep_for(sleeptime);
}
- 两者的区别就是一个参数是时间段,一个参数是未来的时间点。
5.2 std::recursive_timed_mutex:是带超时功能的递归独占互斥量
- 综合了recursive_mutex和timed_mutex的功能,允许同一个线程多次获取这个互斥量,多次lock。且有try_lock_for和try_lock_until函数。