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

Dav*_*rtz 13 c++ concurrency boost boost-thread

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

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

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

class MyClass
{
 private:
  boost::mutex mLock;
 public:
  boost::scoped_lock& ScopedLock(return ScopedLock(mLock));
}
Run Code Online (Sandbox Code Playgroud)

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

MyClass f;
if(foo)
{
 boost::scoped_lock lock(f->GetScopedLock());
 f->LockedFunc1();
 f->LockedFunc2();
}
Run Code Online (Sandbox Code Playgroud)

这个想法是,LockedFunc1并且LockedFunc2将被锁定调用.析构函数lock将解锁f->mLock.

我有两个基本问题:

1)我该怎么做?

2)这是明智的吗?

注意:这与这个类似命名的问题完全不同:返回一个boost :: scoped_lock.

jus*_*tin 11

我怎样才能做到这一点?

备选方案1

一种方法是创建一个具有以下类型的类型boost::scoped_lock:

class t_scope_lock {
public:
  t_scope_lock(MyClass& myClass);
  ...
private:
  boost::scoped_lock d_lock;
};
Run Code Online (Sandbox Code Playgroud)

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

备选方案2

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

备选方案3

有时最好为其添加一个抽象层MyClass.如果类是复杂的,这可能不是一个好的解决方案,因为你需要提供许多变体,如下所示:

{
 boost::scoped_lock lock(f->GetScopedLock());
 f->LockedFunc1();
 f->LockedFunc2();
}
Run Code Online (Sandbox Code Playgroud)

备选方案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();
Run Code Online (Sandbox Code Playgroud)

这是明智的吗?

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

备选方案#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;
}
Run Code Online (Sandbox Code Playgroud)