我怎样才能返回一个范围锁?

时间:2011-11-11 04:31:09

标签: c++ boost concurrency boost-thread

考虑一下帐户余额的集合。然后你有一个复杂的功能,需要检查几个不同帐户的余额,然后调整几个不同帐户的余额。对于集合的其他用户,操作需要是原子的。你有一个集合类,其主要工作是提供这种原子性。什么是'正确'的方式?

我有一个有一个boost :: mutex成员的类。问题是调用者可能需要在持有互斥锁的同时对类执行一系列调用。但是我不想让类外的代码在互斥体上自由统治。

我想做的是这样的事情(伪代码):

class MyClass
{
 private:
  boost::mutex mLock;
 public:
  boost::scoped_lock& ScopedLock(return ScopedLock(mLock));
}

这样,来电者可以这样做:

MyClass f;
if(foo)
{
 boost::scoped_lock lock(f->GetScopedLock());
 f->LockedFunc1();
 f->LockedFunc2();
}

我们的想法是,在保持锁定的情况下调用LockedFunc1LockedFunc2lock的析构函数将解锁f->mLock

我有两个基本问题:

1)我该怎么做?

2)这是明智的吗?

注意:这与名称相似的问题完全不同:return a boost::scoped_lock

3 个答案:

答案 0 :(得分:11)

我该怎么做?

备选方案1

一种方法是创建一个具有boost::scoped_lock

的类型
class t_scope_lock {
public:
  t_scope_lock(MyClass& myClass);
  ...
private:
  boost::scoped_lock d_lock;
};

MyClass授予对此类型的互斥锁的访问权限。如果此类是专门为MyClass编写的,那么我只需将其添加为内部类MyClass::t_scoped_lock

备选方案2

另一种方法是创建一个与范围锁一起使用的中间类型,它可以转换为(自定义)范围锁的构造函数。然后类型可以选择他们认为合适的。很多人可能不喜欢自定义范围锁定,但它可以让您轻松指定访问权限,并且具有良好的控制能力。

备选方案3

有时候为MyClass添加抽象层会更好。如果这个类很复杂,这可能不是一个好的解决方案,因为你需要提供很多类似的变体:

{
 boost::scoped_lock lock(f->GetScopedLock());
 f->LockedFunc1();
 f->LockedFunc2();
}

备选方案4

有时你可以使用另一个锁(例如内部和外部)。

备选方案5

与#4类似,在某些情况下可以使用递归锁或读写锁。

备选方案6

您可以使用锁定的包装类型有选择地授予对该类型接口部分的访问权限。

class MyClassLockedMutator : StackOnly {
public:
    MyClassLockedMutator(MyClass& myClass);
// ...
    void LockedFunc1() { this->myClass.LockedFunc1(); }
    void LockedFunc2() { this->myClass.LockedFunc2(); }
private:
    MyClass& myClass;
    boost::scoped_lock d_lock; // << locks myClass
};

MyClass f;
MyClassLockedMutator a(f);

a.LockedFunc1();
a.LockedFunc2();

这是明智的吗?

请记住,我不知道你的程序的确切限制是什么(因此,有多种选择)。

备选方案#1,#2,#3和#6(几乎)没有性能开销,并且在许多情况下具有边际附加复杂性。然而,对于客户来说,它们在语法上是嘈杂的。 IMO,强制正确性,编译器可以检查(根据需要)比最小化语法噪声更重要。

替代方案#4和#5是引入额外开销/争用或锁定/并发错误和错误的好方法。在某些情况下,这是一个值得考虑的简单替代。

当正确性,性能和/或其他限制至关重要时,我认为抽象或封装这些复杂性是完全合理的,即使它花费了一些语法噪音或抽象层。我这样做是因为即使我已经编写并维护了整个程序,也很容易引入重大变化。对我来说,这是一个更精细的可见性案例,如果使用得当,也是完全合理的。

一些例子

向下滚动到main - 此示例相当混乱,因为它在一个示例中演示了几种方法:

#include <iostream>
#include <boost/thread.hpp>

class MyClass;

class MyClassOperatorBase {
public:
    /* >> public interface */
    bool bazzie(bool foo);
protected:
    MyClassOperatorBase(MyClass& myClass) : d_myClass(myClass) {
    }

    virtual ~MyClassOperatorBase() {
    }

    operator boost::mutex & ();

    MyClass& getMyClass() {
        return this->d_myClass;
    }

    const MyClass& getMyClass() const {
        return this->d_myClass;
    }

protected:
    /* >> required overrides */
    virtual bool imp_bazzie(bool foo) = 0;
private:
    MyClass& d_myClass;
private:
    /* >> prohibited */
    MyClassOperatorBase(const MyClassOperatorBase&);
    MyClassOperatorBase& operator=(const MyClassOperatorBase&);
};

class MyClass {
public:
    MyClass() : mLock() {
    }

    virtual ~MyClass() {
    }

    void LockedFunc1() {
        std::cout << "hello ";
    }

    void LockedFunc2() {
        std::cout << "world\n";
    }

    bool bizzle(bool foo) {
        boost::mutex::scoped_lock lock(this->mLock);

        return this->imp_bizzle(foo);
    }

protected:
    virtual bool imp_bizzle(bool foo) {
        /* would be pure virtual if we did not need to create it for other tests. */
        return foo;
    }

private:
    class t_scope_lock {
    public:
        t_scope_lock(MyClass& myClass) : d_lock(myClass.mLock) {
        }

    private:
        boost::mutex::scoped_lock d_lock;
    };
protected:
    friend class MyClassOperatorBase;
private:
    boost::mutex mLock;
};

MyClassOperatorBase::operator boost::mutex & () {
    return this->getMyClass().mLock;
}

bool MyClassOperatorBase::bazzie(bool foo) {
    MyClass::t_scope_lock lock(this->getMyClass());

    return this->imp_bazzie(foo);
}

class TheirClassOperator : public MyClassOperatorBase {
public:
    TheirClassOperator(MyClass& myClass) : MyClassOperatorBase(myClass) {
    }

    virtual ~TheirClassOperator() {
    }

    bool baz(bool foo) {
        boost::mutex::scoped_lock lock(*this);

        return this->work(foo);
    }

    boost::mutex& evilClientMove() {
        return *this;
    }

protected:
    virtual bool imp_bazzie(bool foo) {
        return this->work(foo);
    }

private:
    bool work(bool foo) {
        MyClass& m(this->getMyClass());

        m.LockedFunc1();
        m.LockedFunc2();
        return foo;
    }
};

class TheirClass : public MyClass {
public:
    TheirClass() : MyClass() {
    }

    virtual ~TheirClass() {
    }

protected:
    virtual bool imp_bizzle(bool foo) {
        std::cout << "hallo, welt!\n";
        return foo;
    }
};

namespace {
/* attempt to restrict the lock's visibility to MyClassOperatorBase types. no virtual required: */
void ExampleA() {
    MyClass my;
    TheirClassOperator their(my);

    their.baz(true);

// boost::mutex::scoped_lock lock(my); << error inaccessible
// boost::mutex::scoped_lock lock(my.mLock); << error inaccessible
// boost::mutex::scoped_lock lock(their); << error inaccessible

    boost::mutex::scoped_lock lock(their.evilClientMove());
}

/* restrict the lock's visibility to MyClassOperatorBase and call through a virtual: */
void ExampleB() {
    MyClass my;
    TheirClassOperator their(my);

    their.bazzie(true);
}

/* if they derive from my class, then life is simple: */
void ExampleC() {
    TheirClass their;

    their.bizzle(true);
}
}

int main(int argc, const char* argv[]) {
    ExampleA();
    ExampleB();
    ExampleC();
    return 0;
}

答案 1 :(得分:0)

首选解决方案是这样的原子函数:

void MyClass::Func_1_2( void )
{
   boost::lock_guard<boost::mutex> lock( m_mutex );
   LockedFunc1();
   LockedFunc2();
}

您可能需要提供其中一些额外的方法。原则:最好是隐藏用户的锁定策略。如果您发现创建特殊方法是不合理的,您可能需要重新考虑您的设计,并在更高层次上进行抽象。

如果您有合理的理由保持接口相同,请隐藏辅助类后面的锁定详细信息。两个例子。

隐藏传递给需要锁定的方法的令牌类后面的锁。

MyClass my_class;

{
   LockMyClass locked( my_class );
   myclass.Func1( locked ); // assert or throw if locked is not locking my_class
   myclass.Func2( locked );
}

创建一个锁定的接口类,该类是MyClass的朋友:

MyClass my_class;

{
   LockedMyClass locked( my_class );
   locked.Func1(); 
   locked.Func2();
}
  

这是明智的吗?

如果您小心谨慎,但通常您不希望在课堂外公开同步详细信息。可能会出现太多问题。 Sun在java.util.Vector尝试了类似的想法,但后来转向了更好的技术。

答案 2 :(得分:0)

这就是我目前计划这样做的方式。我将创建一个可以返回的ScopedLock类。要使用它,类必须具有boost::mutex并使用该互斥锁构造返回ScopedLock。调用者使用该函数构造自己的ScopedLock,调用者的ScopedLock继承由类成员函数创建的锁。

指针是安全的,因为ScopedLock不能超过您调用其成员函数来获取它的类成员的生命周期。并且(通过班级的逻辑)保证只有一个解锁。

我看到的唯一真正的问题是故意滥用。例如,如果有人从他们的ScopedLock中构造了一个新的ScopedLock,导致另一个ScopedLock(可能具有更长的寿命)继承它不应该具有的锁。 (当我这样做时会很痛。所以不要这样做。)

class ScopedLock {
private:
    boost::mutex *mMutex;   // parent object has greater scope, so guaranteed valid
    mutable bool mValid;

    ScopedLock();           // no implementation

public:
    ScopedLock(boost::mutex &mutex) : mMutex(&mutex), mValid(true) {
        mMutex->lock();
    }

    ~ScopedLock() {
        if(mValid) mMutex->unlock();
    }

    ScopedLock(const ScopedLock &sl) {
        mMutex=sl.mMutex;
        if(sl.mValid)
        {
                mValid=true;
                sl.mValid=false;
        }
        else mValid=false;
    }

    ScopedLock &operator=(const ScopedLock &sl)
    { // we inherit any lock the other class member had
        if(mValid) mMutex->unlock();
        mMutex=sl.mMutex;
        if(sl.mValid) {
            mValid=true;
            sl.mValid=false;
        }
    }
};

我觉得还是有点不对劲。我认为Boost的重点是为你最有可能需要做的所有事情提供一个干净的界面。对我来说,这似乎是非常例程。事实上没有干净的方法让我感到害怕。

更新:&#34;更正&#34;提升方法是使用shared_ptr来锁定持有者对象。当最后一个指针被销毁时,该对象将消失,释放锁定。