在c++中,通过构造std::mutex的实例来创建互斥元,调用成员函数lock()来锁定它,调用成员函数unlock()来解锁它。 但是这种直接调用成员函数是博主不推荐的一种做法,这意味着你必须在离开函数的每条路径上都调用unlock(),包括由于异常导致的在内。 作为替代,标准c++库提供了std::lock_guar类模板。实现了互斥元的RAII惯用语法(资源获取就是初始化,构造时锁定所给的互斥元,析构时解锁),看一个简单的例子
#include <iostream>
#include <thread>
#include <mutex>
#include <algorithm>
#include <list>
std::list<int> g_list;
std::mutex g_mutex;
void add(int value)
{
std::lock_guard<std::mutex> guard(g_mutex);
g_list.push_back(value);
}
bool list_contain(int find_value)
{
std::lock_guard<std::mutex> guard(g_mutex);
return std::find(g_list.begin(),g_list.end(),find_value) != g_list.end();
}
int main()
{
std::thread my_thread1(add,1123);
std::thread my_thread2(add,107);
std::thread my_thread3(add,817);
std::thread my_thread4(add,2018);
std::thread my_thread5(add,1314);
std::thread my_thread6(list_contain,1314);
my_thread1.join();
my_thread2.join();
my_thread3.join();
my_thread4.join();
my_thread5.join();
my_thread6.join();
return 0;
}
全局变量g_list被相应的std::mutex的全局实例保护着。
在add()和list_contain()函数中对std::lock_guard<std::mutex>的使用起到了各个函数中的访问是互斥的,list_contain()将无法在add()进行修改期间看到该列表,除非mutex实例析构了
但是这种有个很明显的问题,如果其中一个成员函数返回对受保护数据的指针或者引用,那么能够访问改指针和引用的任何代码都可以访问甚至操作手保护的数据而无需解锁该互斥元
综上所述,在使用互斥元保护数据的时候需要仔细设计接口,以确保在有任意对受保护的数据进行访问之前,互斥元已经锁定,且不留后门,这样看来用互斥元保护数据不是简单的在每个函数中塞进去一个std::lock_guard对象那么简单。
下面写一个绕过保护无需解锁互斥元即可操作受保护数据的实例
#include <iostream>
#include <thread>
#include <mutex>
class change_data
{
public:
void plag() { a = 1123; b = "love"; }
private:
int a;
std::string b;
};
class data_guard
{
public:
template <typename Function>
void mutex_change_data(Function func)
{
std::lock_guard<std::mutex> guard(m_mutex);
func(m_cd);
}
private:
change_data m_cd;
std::mutex m_mutex;
};
change_data *p = NULL;
void malicious_function(change_data &cd)
{
p = &cd;
}
int main()
{
data_guard _dg;
_dg.mutex_change_data(malicious_function);
p->plag();
return 0;
}
看一下在执行p->plag前后_dg中的受保护变量m_cd的变化情况
可以看出,受保护的数据通过引用传参的形式脱离了lock_guard的保护范围,所以不要将受保护数据的指针或者引用传递到锁保护的范围之外。
比如:
①返回值为引用或者指针
②参数传递中为指针或者引用
人,总是要有一点精神的,不是吗