[译]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加上

引用的分配是差不多的。




 

 

 

 

 

 

 

作者:lgp88 发表于2012-3-6 14:39:07 原文链接
阅读:217 评论:1 查看评论

 

posted @ 2012-03-06 14:39  Moon_Bird  阅读(1473)  评论(0)    收藏  举报