[译]c++线程安全访问控制
译自:http://accu.org/var/uploads/journals/overload104.pdf
这篇文章提供了一种C++模板解决方案来确保在多线程程序中同步访问变量。当我们使用c++编写多线程程序的时候,我们一般会采用如下的模型:
#include <mutex> using namespace std; class Person { public: string GetName() const { unique_lock<mutex> lock(mutex); return mutex; } void SetName(const string& name) { unique_lock<mutex> lock(mutex); this->name = name; }
private: mutex mutex; string name; };
每次当我们想要访问成员变量的时候,我们必须确保先获得了互斥锁。然而,这种模型容易出错。
当我们在使用互斥量时忘记上锁的时候,编译器不会给我们发出警告。
我们需要一种机制来确保同步访问这个成员变量,并且是在锁域内。
这种机制基本是思想是使用两个类:一个作为访问控制器来自动给互斥量加锁及解锁,另一个则作为变量的封装类,
它包含互斥量和这个互斥量所要保护的变量,这个封装器阻止任何对象对这个变量的访问,当然除了这个访问器。这个封装器代码如下:
#include <mutex>
using namespace std;
template<typename T>
class unique_access_guard
{
//nocopyable
private:
unique_access_guard(const unique_access_guard&){}
unique_access_guard& operator=(const unique_access_guard&){};
public:
unique_access_guard():value() {}
unique_access_guard(const T& value)
:value(value)
{}
template<typename A1>
unique_access_guard(const A1& a1 )
:value(a1)
{}
template<typename A1,typename A2>
unique_access_guard(const A1& a1,const A2& a2)
:value(a1,a2)
{}
//Add Constructors with more arguments,
//or use c++0x variadic templates
private:
friend class unique_access;
mutex mutex;
T value;
};
这个封装器类中能初始化一个值,但是不允许任何对象访问(声明为private),同时使用模板构造函数来避免初始化的时候临时对象的生成(例如T为struct 时)。
访问控制器实现的是一个简单的只能指针,代码实现如下:
template<typename T>
class unique_access
{
//nocopyable
private:
unique_access(const unique_access&){}
unique_access& operator=(const unique_access&){}
public:
unique_access(const unique_access_guard& guard )
:lock(guard.mutex)
,valueRef(guard.value)
{}
T& operator*( )
{
return valueRef;
}
T* operator-> ()
{
return &valueRef;
}
private:
unique_lock<mutex> lock;
T& valueRef;
};
让我们用一个例子来展示这两个类是如何工作的,我们假设已经定义好了const_unique_access类。我们构造一个SafePerson类:
using namespace std;
class SafePerson
{
public:
string GetName() const
{
const_unique_access<string> name(nameGuard);
return *name;
}
void SetName(const string& newName )
{
unique_access<string> name(nameGuard);
*name = newName;
}
private:
unique_access_guard<string> nameGuard;
};
同Person相比,SafePerson中,如果没有给互斥变量上锁,那我们是不能够访问这个变量的,因此,当我们使用一个新的成员函数扩展这个类的时候,我们必须
得给变量上锁。
如果我们的类中拥有很多的成员变量,那么将会发生什么事呢?这依赖于我们的需求。如果成员变量时相互独立的,那么我们可以声明更多的访问保护,并且使用
分离的访问器来访问它们。一个更通用的场景是当我们想要给所有的成员变量上锁的时候,而此时我们去访问它们之间的一个或多个。在这种情况下,我们将使用一个
struct来代替它,这个struct内嵌在class中,如以下代码所示:
using namespace std;
class SafeMemberPerson
{
public:
SafeMemberPerson(unsigned int age)
:memberGuard(age)
{}
string GetName() const
{
const_unique_access<Member> member(memberGuard);
return member->name;
}
void SetName(const string& newName )
{
unique_access<Member> member(memberGuard);
member->name = newName;
}
private:
struct Member
{
Member(unsigned int age):age(age) {}
string name;
int age;
};
unique_access_guard<Member> memberGuard;
};
另一个通用的场景是私有成员变量的使用,使用正常的mutex和locked方法,我们必须确保在我们调用helper函数的时候成员变量时锁定的。确保这个的通用方法
是在注释中提前做出声明!有了访问保护器我们就能够是这个前提条件显性化了,有两种方案:一是就像在HelperPerson类中所表示的那样,通过引用传递
const_unique_access对象给helper函数,现在我们在没有拥有访问器或锁的情况下是不可能调用helper函数。如以下代码所示:
using namespace std;
class HelperPerson
{
public
HelperPerson(unsigned int age):memberGuard(age)
{}
string GetName( ) const
{
const_unique_access<Member> member(memberGuard);
Invalidate(member);
return member;
}
void SetName(const string& newName )
{
unique_access<Member> member<memberGuard>;
Invalidate(member);
member->name = newName;
}
private:
void Invalidate(const_unique_access<Member>& member) const
{
if(member->age < 0)
throw runtime_error("Age can not negative!\n");
}
struct Member
{
Member(unsigned int age):age(age) { }
int age;
string name;
};
unique_access_guard<Member> memberGuard;
};
另一种解决方案是,如MemberHelperPerson所示,那就是使helper成为Member的成员函数。当我们拥有访问器的时候,只要我们解引用Member结构体,我们就能保
证,在Member内的任何成员函数只在我们有锁的情况下调用运行,代码如下所示:
using namespace std;
class MemberHelperPerson
{
public
MemberHelperPerson(unsigned int age):memberGuard(age)
{}
string GetName( ) const
{
const_unique_access<Member> member(memberGuard);
member->Invalidate();
return member;
}
void SetName(const string& newName )
{
unique_access<Member> member<memberGuard>;
member->Invalidate();
member->name = newName;
}
private:
struct Member
{
Member(unsigned int age):age(age) { }
void Invalidate( ) const
{
if(age < 0)
throw runtime_error("Age can not negative!\n");
}
int age;
string name;
};
unique_access_guard<Member> memberGuard;
};
以上这些都说明单独访问成员变量,这个框架也很容易扩展共享访问。我们需要一个嵌入了std::shared_mutex的shared_access_guard类,和一个shared_access 访问
器来获得成员变量的共享访问权,如果我们想要互斥访问这些共享的成员变量,然后我们可以在shared_access_guard使用unique_access访问器,const用来确保
const_unque_access和shared_access只是读成员变量,而只能用unique_access去写。使用访问器的系统开销是很小的,一个访问控制器的构造和std::unique_lock加上
引用的分配是差不多的。

浙公网安备 33010602011771号