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

24,245

Solution 1

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)>).

Solution 2

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
    /**
    * @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;
#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 STL components, e.g., std::lock_guard are templates, so they work well with this new class.

Solution 3

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.

Solution 4

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.

Solution 5

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
}
Share:
24,245
Basile Starynkevitch
Author by

Basile Starynkevitch

My resume is downloadable here. See my web page on http://starynkevitch.net/Basile/ ; most of the time I am typing from my home at Bourg-La-Reine. You can email me to [email protected] My pet -hobby- open source project (in artificial intelligence, for Linux) is RefPerSys (still unfunded in end of 2021, and done with others). I dream to have more time to devote to it. You'll make me happy by compiling it on your Linux box, and give feedback; Contact me for potential applications or funding opportunities. I am an enthusiast of free software, and know quite well Linux (which I am using since 1994) I think that high-level languages are important, and that declarative knowledge-based systems should be more used (see J.Pitrat's blog ...) You might contact me by an email to basile at starynkevitch dot net but please give the URL of your question in your message. I am French,I am a man (born in 1959), a husband, a grand father, and have a PhD in CS (artificial intelligence, 1990, Paris LIP6) and graduated from ENS Cachan. My native tongue is French. My parents spoke Russian to me. In Russian my full name is Василий Дмитриевич Старынкевич ; in English my first (christian) name is spelled Basil. If you send me an email to [email protected] use French, or English, or Russian and mention the URL of your question. My Russian spelling is so bad that I dare not use it in emails (so I would answer in English to any Russian email). See https://github.com/bstarynk/ and and https://gitlab.com/bstarynk/ PS. I intend to continue working on persistent reflexive systems during retirement, e.g. in http://refpersys.org/ .... I find H2020 projects excessively bureaucratic: its paperwork is really overwheling. My email is public: [email protected] and I don't use social media. Photo done by my son Matthieu Starynkevitch (oct. 2019)

Updated on December 02, 2020

Comments

  • Basile Starynkevitch
    Basile Starynkevitch over 3 years

    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)