Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return a scoped lock?

Consider, say, a collection of account balances. And then you have a complex function that needs to check the balances of a few different accounts and then adjust the balances of a few different accounts. The operations need to be atomic with respect to other users of the collection. You have a collection class whose primary job is to provide this kind of atomicity. What's the 'right' way?

I have a class that has a boost::mutex member. The problem is that callers may need to perform a series of calls into the class while holding the mutex. But I don't want to give code outside the class free reign on the mutex.

What I'd like to do is something like this (pseudo-code):

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

That way, callers can do this:

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

The idea is that LockedFunc1 and LockedFunc2 would be called with the lock held. The destructor for lock would unlock f->mLock.

I have two basic questions:

1) How can I do this?

2) Is this sensible?

Note: This is completely different from this similarly named question: return a boost::scoped_lock.

like image 859
David Schwartz Avatar asked Nov 11 '11 04:11

David Schwartz


1 Answers

How can I do this?

Alternative 1

One approach would be to create a type which has a boost::scoped_lock:

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

and for MyClass to grant access to the mutex for this type. If this class is written specifically for MyClass, then I'd just add it as an inner class MyClass::t_scoped_lock.

Alternative 2

Another approach would be to create an intermediate type for use with the scope lock which could be convertible to a (custom) scope lock's constructor. Then the types could opt in as they see fit. A lot of people may not like the custom scope lock, but it would allow you to easily specify the access as you desire, and with a good degree of control.

Alternative 3

Sometimes it's better to add an abstraction layer for MyClass. If the class is complex, this is not likely a good solution because you will need to provide a lot of variants which look like:

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

Alternative 4

Sometimes you can use another lock (e.g. internal and external).

Alternative 5

Similar to #4, you can use a recursive or readwrite lock in some cases.

Alternative 6

You can use a locked wrapper type to selectively grant access to portions of the type's interface.

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();

Is this sensible?

Keep in mind that I have no idea what the exact constraints of your program are (hence, multiple alternatives).

Alternatives #1, #2, #3, and #6 have (virtually) no performance overhead, and have marginal additional complexity in many cases. They are, however, syntactically noisy for a client. IMO, forced correctness which the compiler can check (as needed) is more important than minimizing syntactical noise.

Alternatives #4 and #5 are good ways to introduce additional overhead/contention or locking/concurrent errors and bugs. In some cases, it is a simple substitution worth consideration.

When correctness, performance, and/or other restrictions are critical, I think it makes perfect sense to abstract or encapsulate those complexities, even if it costs some syntactical noise or an abstraction layer. I do this because it's too easy introduce breaking changes - even if I have written and maintained the entire program. To me, it's a more elaborate case of visibility, and perfectly sensible if used correctly.

Some Examples

Scroll down to main - this sample is rather disorganized because it demonstrates several approaches in one:

#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;
}
like image 122
justin Avatar answered Sep 28 '22 10:09

justin