Ives
Ives

Reputation: 239

How to specify what mutex locks are needed before entering a function

Sometimes a function is written that requires one or more mutexes to be locked before entering that function. If this requirement is not specified, the function can be called without taking the relevant locks before entering, which could have catastrophic consequences.

Now, it is possible to specify something like this in the documentation of a function, but I really don't like that.

I was thinking to specify it in the preconditions for a function (assert's when entering the function), but what should the condition be?

Even if the std::mutex in C++11 did have a has_lock() function, there still would not be any guarantees that I'm the one who has the lock.

Upvotes: 1

Views: 612

Answers (2)

Peter Ruderman
Peter Ruderman

Reputation: 12485

I think the answer to your dilemma is simply don't use an external mutex. If a class manages a resource that needs to be synchronized, then it should use an internal mutex and handle all the synchronization itself. An external mutex is dangerous since it opens you up to the possibility of both deadlocks and unsynchronized access.

From the comments, it sounds like the problem you're struggling with is refactoring a synchronized collection. You want to move some code out of the class, but that code must be synchronized. Here's an example of how you can do it:

class MyCollection {
private:
    std::list<Foo> items;
    std::mutex lock;

public:
    template <class F> void ForEach( F function )
    {
        std::lock_guard<decltype(this->lock) guard( this->lock );

        for( auto item : items )
            function( *item );
    }
};

This technique still has the potential for deadlock. Since the function parameter is an arbitrary function, it might access the collection and thus acquire the mutex. On the other hand, this behaviour might be desired if "ForEach" should be read-only.

Upvotes: 2

Andy Prowl
Andy Prowl

Reputation: 126502

If you really don't want to use recursive mutexes and your goal is to figure out whether the current thread is holding a mutex without trying to acquire it, defining a mutex wrapper is probably the simples solution. Here is a wild shot:

#include <thread>
#include <mutex>
#include <iostream>

using namespace std;

template<typename M>
struct mutex_wrapper
{
    void lock() 
    { 
        m.lock(); 
        lock_guard<mutex> l(idGuardMutex); 
        threadId = this_thread::get_id();
    }

    void unlock() 
    { 
        lock_guard<mutex> l(idGuardMutex); 
        threadId = thread::id(); 
        m.unlock(); 
    }

    bool is_held_by_current_thread() const 
    { 
        lock_guard<mutex> l(idGuardMutex); 
        return (threadId == this_thread::get_id()); 
    }

private:

    mutable mutex idGuardMutex;
    thread::id threadId;
    M m;
};

And here is a simple example of how to use it:

int main()
{
    cout << boolalpha;
    mutex_wrapper<mutex> m;
    m.lock();
    cout << m.is_held_by_current_thread() << endl;
    m.unlock();
    cout << m.is_held_by_current_thread() << endl;
}

Upvotes: 3

Related Questions