views:

98

answers:

6

Say I have a class A:

class A {
public:
   A();

   void fetch_data() { return 1; }
   void write_x_data() {
     // lock this instance of A
     private_function1_which_assumes_locked();
     private_function2_which_assumes_locked();
     // unlock this instance of A
   }
   void write_y_data() {
     // lock this instance of A
     private_function1_which_assumes_locked();
     // unlock this instance of A
   }
private:
   void private_function1_which_assumes_locked();
   void private_function2_which_assumes_locked();
};

I want to guarantee that private_function*_which_assumed_locked() can never be called unless A is locked.

What's the best way to accomplish this? I've got 5 or so public functions which need locking. These functions never call into each other, so I'm not worried about deadlocking with these. Combined, these 5 public functions call into around 15 different private functions which need to assume the object is in a locked state. Obviously, I can't lock the private functions, since I'd get deadlocks.

Feel free to provide answers in reasonably high-level abstractions, assuming the existences of Mutexes and Scopeguards.

In Python I might do something like this with decorators:

class locked_method(object):
    def __init__(self, f):
        self.f = f

    def __call__(self):
        # Do whatever is needed to lock
        self.f()
        # Do whatever is needed to unlock

class checklocked(object):
    def __init__(self, f):
        self.f = f

    def __call__(self):
        # check locked, if not, don't call self.f(),
        # and yell at me loudly for screwing up.
        self.f()

@locked_method
def func1():
    __function_which_assumes_locked()

@checklocked
def __function_which_assumes_locked():
    pass

NB: I've not done much in the way of Python, so feel free to comment if my Python is wrong/stupid, but the point of this question is more the best way to accomplish this sort of thing in C++, so hopefully the Python provided is enough to give you an idea of what I want to do.

+4  A: 

You could use a locker class, and require one to exist in order to call the private functions:

class A
{
public:
    void write()
    {
        Lock l(this);
        write(l);
    }

private:
    void lock();
    void unlock();
    void write(const Lock &);

    class Lock
    {
    public:
        explicit Lock(A *a) : parent(a) {parent->lock();}
        ~Lock() {parent->unlock();}
    private:
        A *parent;
    };
};
Mike Seymour
+1 - This looks like the best suggestion so far, I'll let you know how I get on!
Dominic Rodger
Insted of writing a locker class, I would suggest using boost's nice lock classes.
caspin
@Caspin: Yes, if you're using Boost.Thread then do that.
Mike Seymour
A: 

You can use '#Define' to create a similar effect:

#define CheckIfLocked(func, criticalSection) \
    // here you can enter the critical section
    func \
    // exit critical section
Dror Helper
A: 

You can use one of C++ locking mechanisms.

Use one of the "Try"-ing functions in as the first step in every private function - these functions just check if the lock is locked. If it isn't locked, throw an exception.

P.S. It could be interesting to make sure the private functions are only called from a locked state in compile-time... This would probably be possible in Haskell :)

Danra
A compile-time check is perfectly possible in C++: see my answer.
Mike Seymour
You're right! Good answer :)
Danra
A: 

Lock the object A once at the beginning of each public functions and release the lock when processing is done. The public functions are the entry points where processing begins anyway. You won't have to worry about locking in each private functions.

Have a look at RAII for locking/unlocking the object (or use something like boost::mutex::scoped_lock).

Houlalala
Yes, but then what if another function which does not do the locking calls that private function? That's the core of the problem.
Dominic Rodger
As long as the processing starts with a public function call, it won't be a problem. Private functions can use other private functions since the object has been locked from the start. How would you access private functions if it's not by going through a public function first?
Houlalala
Yes, but what if a public function which doesn't lock calls into a private function which assumes the object is locked? How would I guarantee at compile time that that couldn't happen?
Dominic Rodger
If your private functions expect the caller to lock object A, you can surly assert the existence of a lock inside the private function.
Sebastian
If you're trying to identify logic errors at compile time, that's a completely different question for which I don't have an answer.
Houlalala
A: 

lock() function locks the mutex and puts thread id into a member variable. unlock() sets the member variable to 0 and unlocks the mutex

In your private functions check that thread id is that of your own thread, and complain loudly if not so.

Arkadiy
+2  A: 

This reminds me of a Dr. Dobb's article by Andrei Alexandrescu.

The idea is to use the fact that, in the same way that a const member function can't call a non-const one, a volatile member can't call a non-volatile one. He then uses volatile to "tag" the thread-safe functions, while non-volatile ones are "unsafe". So this fails to compile:

class Foo
{
public:
    // Not the "volatile"
    int External() volatile
    {
        return this->Internal();
    }

private:

    // Not volatile
    int Internal();
};

because this can't be converted from a Foo volatile* to a Foo*. A cast is necessary. This is made with a helper class that also plays the role as a RAII lock holder. So Foo becomes:

class Foo
{
public:
    int External() volatile
    {
        Lock self(this);
        return self->Internal();
    }

private:

    class Lock{
    public:
        explicit Lock(volatile Foo* Self)
            : m_Mutex.Aquire(),
              m_Self(const_cast<Foo*>(Self))
        {}

        ~Lock()
        {
            m_Mutex.Release();
        }

        Foo* operator->(){
            return m_Self;
        }

    private:
        SomeMutexType m_Mutex;
        Foo* m_Self;
    };


    int Internal();
};
Éric Malenfant
It's really a hack. But very clever! +1
Arkadiy