[译]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 查看评论

 

你可能感兴趣的:(线程安全)