6

Current situation

Right now I have a method like

  Data lookupData(Key id)
  {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto it = m_dict.find(id);
    if(it == m_dict.end())
    {
      return Data::Empty;
    }
    else
    {
      return it->second;
    }
  }

Problem

However I now have a situation where, if the result is not found, I want the calling code to be able to do some more stuff while keeping the lock held.

One straightforward solution is to take the mutex lock outside of this method and require calling code to lock the mutex instead, maybe by documenting this in a doc comment or something. However it feels like it would be better to be able to enforce this, by passing some kind of receipt to the method proving that we actually hold a lock on the mutex in question.

Potential solution(s)

So I'm considering doing something like

  Data lookupData(Key id, const std::unique_lock<std::mutex> & lock)
  {
    if(lock.mutex() != m_mutex) { /* throw * }
    if(! lock.owns_lock()) { /* throw */ }
auto it = m_dict.find(id);
if(it == m_dict.end())
{
  return Data::Empty;
}
else
{
  return it-&gt;second;
}

}

Alternatively instead of

     if(! lock.owns_lock()) { /* throw */ }

I could do something like

     if(! lock.owns_lock()) { lock.lock(); }

(obviously removing the const qualifier from the lock parameter) so that the method can be called either with or without the lock being held.

Questions

  • Is the idea of a method requiring proof that a mutex is locked a good one, or if not, why not?
  • Assuming the answer to the above is "yes," are the above approaches recommended? If not, why not, and what would be recommended instead?
  • Are there names for the type of problem and/or the general solutions described above that I can Google to read more about this sort of thing?

1 Answers1

2

One solution here is to create a proxy object for the API that should only be available while locked. Something like this:

class Dict
{
  std::mutex m_mutex;
  std::unordered_map<Key, Data> m_dict;
public:
  class Lock
  {
    std::unique_lock<std::mutex> m_lock;
    Dict* m_parent;
  public:
    explicit Lock(Dict& parent)
      : m_lock(parent.m_mutex),
        m_parent(&parent)
    {}
    Data lookup(Key key)
    {
      auto it = m_parent->m_dict.find(id);
      return it == m_parent->m_dict.end() ? Data::Empty : it->second;
    }
  };
  Lock lock()
  { return Lock(*this); }
};

int main() { Dict d; d.lock().lookup(0); auto locked = d.lock(); if(locked.lookup(1)) locked.lookup(2); }

Homer512
  • 121