How to assert if a std::mutex is locked?

With GCC 4.8.2 (on Linux/Debian/Sid 64 bits) -or GCC 4.9 when available - in C++11- I have some mutex

std::mutex gmtx;

actually, it is a static member in some class Foo containing both alpha and beta methods below.

it is locked in alpha like

void alpha(void) {
   std::lock_guard<std::mutex> g(gmtx);
   beta(void);
   // some other work
}

and I want to check in beta that indeed gmtx is locked:

void beta(void) {
   assert (gmtx.is_locked());
   // some real work
}

(notice that is_locked is only called inside assert... It can be very inefficient or even sometimes inaccurate)

Of course, I have other functions calling beta, e.g.

void gamma(void) {
   std::lock_guard<std::mutex> g(gmtx);
   beta();
   // some other work
}

but is_locked does not exist.... How should I define it? (actually I would like to be sure that the mutex has been locked in the same thread by some [indirect] caller...)

(the reason I want to test that with assert is that beta could be called elsewhere)

I cannot use try_lock (unless using recursive mutexes), because in the common case it would lock an already locked mutex... (locked in the same thread by a caller) and this is not only undefined behavior but blocks entirely.

I want to avoid recursive mutexes (more costly than plain mutexes) unless I really have to.


NB: The real program is a bit more complex. Actually, all the methods are inside a class which maintain a naming bi-directional relation on "items". So I have inside that class a map from items to names and another from names to items. beta would be the internal method adding really a naming, and alpha and gamma would be the methods finding -or adding- an item by its name, or a name by its item.

PS: the real program is not yet released, but should become part of MELT - its future monitor; you can download it (alpha stage, very buggy) from here (a temporary location)

Upvotes: 40

Views: 37116

Answers (8)

Pushkoff
Pushkoff

Reputation: 449

You can use std::unique_lock and its method .mutex() to check what mutex it holds.

std::mutex mtx;

void alpha() {
    std::unique_lock<std::mutex> lck(mtx);
    beta(lck);
}

void beta(std::unique_lock<std::mutex>& lck) {
    assert(lck.mutex() == &mtx);
}

in this case you also will see beta(...) needs some lock before call.

Upvotes: -1

Ami Tavory
Ami Tavory

Reputation: 76316

Strictly speaking, the question was about checking the lockedness of std::mutex directly. However, if encapsulating it in a new class is allowed, it's very easy to do so:

class mutex :
    public std::mutex
{
public:
#ifndef NDEBUG
    void lock()
    {
        std::mutex::lock();
        m_holder = std::this_thread::get_id(); 
    }
#endif // #ifndef NDEBUG

#ifndef NDEBUG
    void unlock()
    {
        m_holder = std::thread::id();
        std::mutex::unlock();
    }
#endif // #ifndef NDEBUG

#ifndef NDEBUG
    bool try_lock()
    {
        if (std::mutex::try_lock()) {
            m_holder = std::thread::id();
            return true;
        }
        return false;
    }
#endif // #ifndef NDEBUG

#ifndef NDEBUG
    /**
    * @return true iff the mutex is locked by the caller of this method. */
    bool locked_by_caller() const
    {
        return m_holder == std::this_thread::get_id();
    }
#endif // #ifndef NDEBUG

private:
#ifndef NDEBUG
    std::atomic<std::thread::id> m_holder = std::thread::id{};
#endif // #ifndef NDEBUG
};

Note the following:

  1. In release mode, this has zero overhead over std::mutex except possibly for construction/destruction (which is a non-issue for mutex objects).
  2. The m_holder member is only accessed between taking the mutex and releasing it. Thus the mutex itself serves as the mutex of m_holder. With very weak assumptions on the type std::thread::id, locked_by_caller will work correctly.
  3. Other standard library types like std::lock_guard are templates, so they work well with this new class, because it satisfies the Mutex requirement.

Upvotes: 24

M Carrato
M Carrato

Reputation: 21

It's not technically an assertion, but I've used a similar approach to prevent unlocked access to shared state: add a reference parameter to the lock guard class on the in the unsafe function (beta in your example). Then the function cannot be called unless the caller has created a lock guard. It solves the problem of accidentally calling the function outside of the lock, and it does so at compile time with no races.

So, using your example:

typedef std::lock_guard<std::mutex> LockGuard;
void alpha(void) {
   LockGuard g(gmtx);
   beta(g);
   // some other work
}

void beta(LockGuard&) {
   // some real work
}

void gamma(void) {
   LockGuard g(gmtx);
   beta(g);
   // some other work
}

//works recursively too
void delta(LockGuard& g)
{
   beta(g);
}

Drawbacks:

  • Does not validate that the lock actually wraps the correct mutex.
  • Requires the dummy parameter. In practice, I usually keep such unsafe functions private so this is not an issue.

Upvotes: 2

Tony
Tony

Reputation: 1611

My solution is simple, use try_lock to test then unlock if needed:

std::mutex mtx;

bool is_locked() {
   if (mtx.try_lock()) {
      mtx.unlock();
      return false;
   }
   return true; // locked thus try_lock failed
}

Upvotes: -3

Andrew
Andrew

Reputation: 1

Try atomic (e.g. atomic<bool> or atomic<int>), which has a nice load function that will do what you want, as well as other nice functions like compare_exchange_strong.

Upvotes: 2

yohjp
yohjp

Reputation: 3135

std::unique_lock<L> has owns_lock member function (equivalent of is_locked as you say).

std::mutex gmtx;
std::unique_lock<std::mutex> glock(gmtx, std::defer_lock);

void alpha(void) {
   std::lock_guard<decltype(glock)> g(glock);
   beta(void);
   // some other work
}
void beta(void) {
   assert(glock.owns_lock()); // or just assert(glock);
   // some real work
}

EDIT: In this solution, all lock operations should be performed via unique_lock glock not 'raw' mutex gmtx. For example, alpha member function is rewritten with lock_guard<unique_lock<mutex>> (or simply lock_guard<decltype(glock)>).

Upvotes: 17

bames53
bames53

Reputation: 88195

Well, if the expense of the assertion really isn't an issue then you can just call try_lock() from another thread where its behavior is guaranteed to be well defined:

void beta(void) {
  assert(std::async(std::launch::async, [] { return gmtx.try_lock(); })
                 .get() == false &&
         "error, beta called without locking gmtx");
  // some real work
}

Upvotes: 2

Nevin
Nevin

Reputation: 4863

You could just use a recursive_mutex, which can be locked multiple times on the same thread. Note: If it were my code, I would restructure it so that I don't need a recursive_mutex, but it will address your problem.

Upvotes: 15

Related Questions