在c++中,通过构造std::mutex的实例来创建互斥元,调用成员函数lock()来锁定他,调用unlock()来进行解锁。不过直接调用成员函数是不推荐的做法,因为这意味着你必须记住在每条可能离开函数(即线程)的代码路径上提供std::unlock(),包括由于异常所导致在内的。作为替代,c++提供了std::lock_guard类模板,实现了RAII惯用方法;它在构造函数时锁定互斥元,在析构是解锁互斥元,从而保证被锁定的互斥元始终被锁定和解锁。
然而,用互斥元保护数据并不只是像在每个成员函数中拍进一个std::lock_guard对象那样容易,一个迷路的指针或者引用,所有的保护都将白费。如下面的实例那样意外的传出对受保护数据的引用:
class some_data
{
private:
int a;
std::string b;
public:
void do_something();
};
class data_wrapper
{
private:
some_data data;
std::mutex m;
public:
template<typename Function>
void process_data(Function func){
std::lock_guard<std::mutex> l(m);
func(data); //传递“受保护的”数据到用户提供的函数
}
};
some_data* unprotected;
void malicious_function(some_data& protected_data){
unprotected = &protected_data;
}
data_wrapper x;
void foo(){
x.process_data(malicious_function);
unprotected->do_something();
在上面的实例中,process_data中的代码看似无害,受到std::lock_guard很好的保护,但对用户提供的函数func的调用就意味着foo可以传入malicious_function来绕过保护,然后无需锁定互斥元即可调用do_something()。
从根本上来说,这个代码的问题在于他没有完成你所设置的内容,标记所有访问该数据结构的代码为互斥的。在这个例子中,忽略了foo()中调用unprotected->do_something()的代码。不幸的是,这部分代码不是c++线程库所能帮助你的,而是取决于作为程序员的我们,去锁定正确的互斥元来保护你的数据。你有一个可遵循的准则:不要讲对受保护数据的指针和引用传递到锁的范围之外,无论是通过函数中返回他们、将其存放在外部可见的内存中,还是作为参数传递给用户提供的函数。