在C++中,通过构造std::mutex的实例创建互斥元。并且在C++库中提供了std::lock_guard类模板,实现了互斥元的RAII用法,即它在构造时锁定所给的互斥元,在析构时将互斥元解锁,从而保证被锁定的互斥员始终被正确解锁。典型的代码如下所示:
#include<list>
#include<mutex>
#include<algorithm>
clsss Test
{
public:
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
std::lock_guard<std::mutex> guard(some_mutex);
return std::find(some_list.begin(), some_list.end(), vlaue_to_find) != some_list.end);
}
private:
std::list<int>some_list;
std::mutex some_mutex;
};
在上述代码中,class Test定义了一个成员变量some_list; 定义了一个互斥元some_mutex. 成员函数add_to_list和list_contains在访问共享数据some_list时都通过互斥元保护。
如果此时,class Test新增一些成员函数,其中的一个成员函数返回了对受保护数据的指针或者引用,那么即使其它所有的成员函数在访问共享数据时做了很好的保护,整个对共享数据的保护将白费。因为,外部代码可以通过该指针或者引用访问受保护的数据而无需锁定互斥元。如下所示:
#include<list>
#include<mutex>
#include<algorithm>
clsss Test
{
public:
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
std::lock_guard<std::mutex> guard(some_mutex);
return std::find(some_list.begin(), some_list.end(), vlaue_to_find) != some_list.end);
}
std::list<int> *get_list() //新增接口,返回共享数据的指针
{
std::lock_guard(std::mutex) guard(some_mutex);
return &some_list;
}
private:
std::list<int>some_list;
std::mutex some_mutex;
};
int main()
{
Test test;
test.add_to_list(1);
std::list<int> *data = test.get_list(); //拿到共享数据指针
data->push_back(2); //修改共享数据而不需要经过互斥元
return 1;
}
然而,你可能会说,只要检查代码,没有一个成员函数通过其返回值或者输出参数,返回受保护的数据的指针或者引用给其它调用者,数据就安全了。其实,没有这么简单。因为返回共享数据的指针或者引用,有时并没有那么直观。如下面所示:
#include<list>
#include<mutex>
#include<algorithm>
using Func = void (*)(std::list<int> *);
clsss Test
{
public:
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex);
some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
std::lock_guard<std::mutex> guard(some_mutex);
return std::find(some_list.begin(), some_list.end(), vlaue_to_find) != some_list.end);
}
void process_data(Func func)
{
std::lock_guard<std::mutex> guard(some_mutex);
func(some_list); //传递受保护的数据到外部提供的函数
}
private:
std::list<int>some_list;
std::mutex some_mutex;
};
std::list<int>* unprotected;
void malicious_function(std::list<int> *protected_data)
{
unprotected = protected_data; //将受保护的数据拷贝出来
}
int main()
{
Test test;
test.add_to_list(1)
test.process_data(malicious_function); //传递一个恶意函数
protected_data->push_back(2); //对受保护的数据进行修改,绕过互斥元的保护
}
所以,除了检查成员函数有没有向其调用者传出指针和引用,检查它们有没有向其调用的不在掌握的函数传入指针和引用,也同样重要。那些函数可能将指针和引用存储在某个地方,将来可以脱离互斥元的保护而被使用。
总结: 要想互斥元对共享数据做到好的保护,不要将对共享数据的指针和引用传递到锁的范围之外,无论是通过从函数中返回它们、将其存放在外部可见的内存中,还是作为参数传递给外部提供的函数。