2

I have a function foo() which is called from different places in a heavy multi-threaded environment.

It is modifying some sensitive data but due to the complexity I cannot use inside this function std::lock_guard... - this must be assured by the caller function. The reason is that the function is called several times in a row and the modified data [inside foo()] must not be touched in the meantime by other thread until the one thread finished all modifications by the several calls of foo().

We all are human - and especially me - and I was thinking about a way how to assure that the function foo() is indeed under the lock_guard... control and I just didn't forget to lock the scope.

For me - running under gcc and I don't have to consider any platform independent solution - I found this way:

auto foo( __attribute__ ((unused)) std::lock_guard<std::mutex> & guardActive ) -> void
{
   ...
}

Then, foo() must be called like this, otherwise already gcc does not compile it:

{
    std::lock_guard<std::mutex> lockScope( mutexObject );
    // block of commands calling foo() which must NOT be interrupted
    ...
    foo( lockScope );
    ...
    foo( lockScope );
    ...
    foo( lockScope );
    ...
}

My question:

Is this way OK, or are there better solutions? Reentrant mutex cannot be used.

Peter VARGA
  • 4,780
  • 3
  • 39
  • 75
  • 2
    Since no operations are defined on `lock_guard`, there seems to be no reason you are passing `lockScope` (by reference) to `foo()` – LWimsey Jan 30 '17 at 23:18
  • @LWimsey But how do I **assure** `foo()` is running under `lock_guard`? When I don't pass `lockScope` to `foo()` there is the *danger* I forget to lock in the caller function the several calls of `foo()`. – Peter VARGA Jan 30 '17 at 23:21
  • 1
    Requiring the passing of an object just to ensure that it has been created is not an uncommon pattern. Unfortunately I can't remember its name; I thought it was called Witness but it isn't. I think it's a good pattern. – Sebastian Redl Jan 30 '17 at 23:22
  • @AlBundy Since you create the `lock_guard` object before calling `foo()`, that particular instance is under protection of the `mutex`.. Passing it by reference does not change that, although it might indeed help to assure you create the object before every call to `foo()` – LWimsey Jan 30 '17 at 23:29
  • @AlBundy Calling `foo()` multiple times with a single `lock_guard` object serializes the calls. As long as `foo()` is not called recursively, the easiest is to create the guard object inside `foo()`. Calling it multiple times from the same function is not a problem since it is released after each call. I would go with David's answer – LWimsey Jan 30 '17 at 23:35
  • @LWimsey It is **not** possible in a heave multi-threaded environment! No way it works! Check my updated code! – Peter VARGA Jan 30 '17 at 23:37
  • Being similarly mistake-prone, I've used similar ideas to lock data owned by a particular object. You may find this interesting reading: http://www.boost.org/doc/libs/1_63_0/doc/html/thread/synchronization.html; in particular the "External Locking" section. – mkal Jan 31 '17 at 01:36

3 Answers3

2

I am convinced, the way I started was correct. I found this post On unnamed parameters to functions, C++ which more or less confirms me.

Now, I removed the parameter name and it is implemented like this:

auto foo( std::lock_guard<std::mutex> & ) -> void
{
   ...
}

which I find more elegant because it does not use the gcc extension and it is full C++ compatible.

Community
  • 1
  • 1
Peter VARGA
  • 4,780
  • 3
  • 39
  • 75
  • I don't understand what passing in an `std::lock_guard` helps. It has no usable members. Why not `void foo();`? – Persixty Aug 15 '17 at 07:26
  • @Persixty It assures that there is an existing `std::lock_guard` object and therefore `foo()` runs under the lock. Due to the fact the lock is not needed in `foo()` there is no need to name the parameter. – Peter VARGA Aug 15 '17 at 10:05
  • I don't think parameters are for 'assuring' some object exists. It doesn't evidence that the right lock his held. You could (for example) use the any guard. The right model is a transaction wrapper. I'll do an answer... – Persixty Aug 15 '17 at 10:29
  • 1
    @Persixty The solution works for me and is very simple. It _remembers_ me I have to use a lock when calling `foo()`. Of course, I can take a knife and cut my finger - why should I do it and why should I abuse the parameter? You understand? – Peter VARGA Aug 15 '17 at 13:36
  • I see what you're doing and hey, it works for you as a reminder. If it was a released library I'd say it was eccentric. I'm going to provide an answer using a 'facade' pattern because it's a more formal way of achieving this. Essentially you make a transaction object that controls the mutex. In code where that's OTT I create methods called `foo_prelocked()` and comment that the lock must be held on call. That's suitable for implementing members that may be called individually such as `foo()` that can be called standalone and locks/unlocks and `foo_prelocked()` in nested calls. – Persixty Aug 15 '17 at 13:50
  • 1
    What happens when someone comes along and writes: `{ std::mutex local_mutex; std::lock_guard local_guard(local_mutex); foo(local_guard); }`? Hint: bad things – Caleth Aug 16 '17 at 10:43
  • @Caleth Why should I write code like this? What happens if I take a gun and shoot myself? Hint: bad things. What are you trying to tell me? Again: This is a function for my project which is *NOT* a part of any public library and it never will be. It should only _remind_ me that I have to lock the repeated calls of `foo()`. – Peter VARGA Aug 16 '17 at 15:17
  • Well mostly because I think your *whole design* is flawed, if you have to go through all these hoops. This is also a rather dangerous solution, the audience for this Q&A is not just *you*. – Caleth Aug 16 '17 at 15:28
  • This approach can be made safer, e.g. by locking a singleton mutex: `class FooMutex { public: static FooMutex & get(){ static FooMutex instance; return instance; } void lock() { mut.lock(); } void unlock(){ mut.unlock(); } private: std::mutex mut; }` then `void foo(std::lock_guard &)` really does **force** you to acquire the correct mutex – Caleth Aug 16 '17 at 15:32
1

The most comprehensive approach is to create a 'transaction object'. It's a variant of the 'Delegation Pattern' in which the transaction object coordinates access to an object. It can be thought of as lock_guard<> on speed. Like lock_guard<> it makes use of RAII to lock and then unlock the object allowing multiple actions to take place inbetween. Also like lock_guard<> optimization is likely to just unwrap all the inline calls and result in code exactly as performant as directly managing the mutex.

This example uses private to forbid access to the 'lock required' members outside a transaction.

In many cases this will surely be over-engineering particularly given a full implementation requires FooConstTrans and FooTrans classes depending on the const qualification of the referenced object.

#include <iostream>
#include <mutex>
#include <type_traits>

class FooTrans;

class Foo {
public:
    Foo():m_x(0)
    {}

friend class FooTrans;    
private:
    int get_x() const{
        return m_x;
    }

    void set_x(int x){
        m_x=x;
    }

    void lock() const{
        m_mutex.lock();
    }

    void unlock() const{
        m_mutex.unlock();
    }

private:
    mutable std::mutex m_mutex;
    int m_x;
};

class FooTrans final {
public: 
    FooTrans(Foo& foo):m_foo(foo){
        m_foo.lock();
    }

    void set_x(int x){
        m_foo.set_x(x);
    }

    int get_x(){
        return m_foo.get_x();
    }

    ~FooTrans(){
        m_foo.unlock();
    }


    private:
      Foo& m_foo;
};

int main() {

    Foo foo;
    FooTrans ft(foo);

    ft.set_x(3);
    //ft is guaranteeing the next line outputs '3' even if other threads are trying to access foo.
    std::cout << ft.get_x() <<std::endl;

    return 0;
}
Persixty
  • 8,165
  • 2
  • 13
  • 35
  • 1
    It is breaking a fly on a wheel! My solution - which does not need *ONE* line more of code - meets totally my requirements. – Peter VARGA Aug 16 '17 at 15:21
  • @AlBundy I agree it is in your case. But I think the question deserves a comprehensive answer. Your solution is an aide-memoire but no actual security. Mine helps enforce the principle. Though obviously can be broken by declaring multiple transaction objects. I think you're asking a question about a general pattern in concurrent programming that fundamentally has a number of different answers. – Persixty Aug 16 '17 at 15:49
-1

If foo should always use the same mutex, for example if it's a member function and the class holds a mutex, I would go with this method:

void foo() {
    std::lock_guard<std::mutex> lock(someMutex);
    ...
}

Otherwise, it's often pushed to the caller of a method/class to moderate calling functions from different threads externally to the call using a lock like so:

{
    std::lock_guard<std::mutex> lock(someMutex);
    foo();
}

If you really think this is error prone (I don't) you could wrap it in a lambda as a convenience:

auto lockedFoo = [&]{ std::lock_guard<std::mutex> lock(someMutex); foo(); };
David
  • 27,652
  • 18
  • 89
  • 138
  • I cannot use your solution for my case - locking it inside `foo()` because there will follow other calls of `foo()` from the same thread which **must not** be interrupted by other threads. I updated my question. – Peter VARGA Jan 30 '17 at 23:31
  • @AlBundy are you saying `foo` is recursive? – David Jan 30 '17 at 23:32
  • @AlBundy I see. If you need a mutex to protect time outside/between `foo` calls, then you definitely need to just lock outside of the `foo` function. There's no point to accepting a lock as an argument (especially since it functionally serves no purpose). If you want to make it more robust, I suggest a new function which performs the action that locks a mutex and calls foo several times. Perhaps a class which holds a mutex and has different functions which lock and call foo several times. I think your problem is a greater architexture one. – David Jan 30 '17 at 23:35
  • @AlBundy I'd have to see more of your code. This can be improved with good design... maybe a thread safe class that uses foo to accomplish its functions is in order. – David Jan 30 '17 at 23:38
  • In this case I have to repeat the code of `foo()`. I was also thinking about this. But for me it is not an option to repeat a code. Implementing as lambda is also not an option because `foo()` is called from several functions. – Peter VARGA Jan 30 '17 at 23:40