接上篇C++新特性33_死锁产生的原因及避免(线程在等待一个永远都不能成功的条件成立,从而进入到陷入休眠,永远不能被唤醒的状态、通过调整锁的使用顺序解决死锁问题),本篇将会学习互斥体的相关知识。
C++新特性34_递归互斥量recursive_mutex与超时互斥量timed_mutex的使用
1. 互斥量
在C++11中除了提供mutex之外还会提供其他三种加强版的mutex,C++11 中提供以下4种语义的互斥量(mutex):
std::mutex
: 独占的互斥量,不能递归使用std::recursive_mutex
: 递归互斥量,不带超时功能std::timed_mutex
: 带超时的独占互斥量,不能递归使用std::recursive_timed_mutex
: 带超时的递归互斥量。
2. 互斥量重入问题
先看一段代码:
#include "stdafx.h"
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <future>
using namespace std;
class CTest{
public:
void foo1() {
m_mtx.lock();
std::cout << "foo1" << std::endl;
m_mtx.unlock();
}
void foo2() {
m_mtx.lock();
std::cout << "foo2" << std::endl;
m_mtx.unlock();
}
private:
std::mutex m_mtx;
};
int main()
{
//主线程
CTest t;
t.foo1();
t.foo2();
return 0;
}
运行结果:程序依次进入t.foo1()和t.foo2()
研究一个问题,是否可以重复上锁?
对void foo1()
中代码进行修改,使得其重复上锁
void foo1() {
m_mtx.lock();
m_mtx.lock();
std::cout << "foo1" << std::endl;
m_mtx.unlock();
}
单步调试运行结果:当程序运行至第二个锁处报异常崩溃
因此同一个线程无法重复多次进入mutex
。
3. 递归互斥量std::recursive_mutex
C++11考虑到同一线程重复使用互斥量的需求,提供了std::recursive_mutex
允许互斥量的重复进入,但要注意释放问题,进入几次就需要释放几次。
以下代码,主线程进入std::recursive_mutex
只释放一次:
//#include "stdafx.h"
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <future>
using namespace std;
std::recursive_mutex g_mtx;
class CTest {
public:
void foo1() {
//引用计数
g_mtx.lock();//+1
g_mtx.lock();//+1
g_mtx.lock();//+1
std::cout << "foo1" << std::endl;
g_mtx.unlock();//-1
}
private:
};
void foo2() {
g_mtx.lock();//+1
std::cout << "foo2" << std::endl;
g_mtx.unlock();//-1
}
int main()
{
CTest t;
t.foo1();
std::thread thd(foo2);
thd.join();
return 0;
}
运行结果:由于主线程并未完全释放,子线程无法进入
使用建议:
- 递归锁的递归是有计数器,超过了计数器最大值会失败,抛异常
- 比非递归锁效率低
- 递归进入占用递归锁,使得代码逻辑不清晰,引发其他问题。
因此,建议酌情使用。
4. 超时互斥量std::timed_mutex
std::timed_mutex设置了等待超时的机制,之前的互斥量如果无法等待进入机会,会一直阻塞线程,使用std::timed_mutex可以为锁的等待设置一个超时值,一旦超时可以做其他事情。
timed_mutex比mutex主要是多了
try_lock
避免了阻塞,获取不到锁则直接返回falsetry_lock_for()
尝试锁定互斥,若互斥在指定的时限时期中不可用则返回等待一段时间
try_lock_until()
尝试锁定互斥,直至抵达指定时间点互斥不可用则返回一个时间点
示例1:try_lock_for()
#include <thread>
#include <iostream>
#include <chrono>
#include <mutex>
std::timed_mutex test_mutex;
void f()
{
auto now = std::chrono::steady_clock::now();
//如使用test_mutex.try_lock尝试请求test_mutex锁,如果能够获得这个锁就可以进入,否则直接返回
//此处是等一段时间
if (test_mutex.try_lock_for(std::chrono::seconds(1))) {
std::cout << "success!\n";
test_mutex.unlock();
} else {
std::cout << "failed!\n";
}
std::cout << "hello world\n";
}
int main()
{
std::lock_guard<std::timed_mutex> l(test_mutex);
std::thread t(f);
t.join();
}
示例2:try_lock_until()
//#include "stdafx.h"
#include <iostream> // std::cout
#include <thread> // std::thread
#include <mutex> // std::mutex, std::unique_lock
#include <future>
using namespace std;
std::timed_mutex test_mutex;
void f()
{
std::cout << "sub thread start\n";
auto now = std::chrono::steady_clock::now();
test_mutex.try_lock_until(now + std::chrono::seconds(5));
std::cout << "sub thread end\n";
}
int main()
{
std::cout << "main start\n";
std::lock_guard<std::timed_mutex> l(test_mutex);
std::thread t(f);
t.join();
std::cout << "main end\n";
}
5. 带超时的递归互斥量std::recursive_timed_mutex
主要结合了超时和递归。
6. 学习视频地址:互斥量recursive_mutex与timed_mutex的使用