0

I'm trying to lock multiple mutexes without a deadlock while also making sure no threads block forever. From what I understand, std::scoped_lock() calls std::lock() to acquire its locks--which is blocking. I'm worried about the poor, unlucky thread that never catches a break and always fails to acquire all of the locks when multiple locks are needed amidst a lot of concurrent access.

I get that critical sections should be as small and fast as possible; one needs to get in and out as quickly as possible to avoid long blocking times. But surely even locking well-written code and critical sections can stress the mutex resources given enough concurrent requests.

I looked at using std::timed_mutex with std::scoped_lock(), but std::timed_mutex::lock() blocks and doesn't make use of the mutex's timed capability (would require std::timed_mutex::try_lock_for() or std::timed_mutex::try_lock_until(), which as far as I know isn't used by std::scoped_lock()).

So is there a way to lock multiple mutexes and guarantee that no thread will block forever while waiting to lock the mutexes (even if that means it doesn't acquire any locks)? Is there a non-blocking version of std::scoped_lock()? Can you break a blocked std::scoped_lock() call? Should I expect the scenario of a thread blocking forever to be so rare that it's an acceptable risk? Am I thinking too hard?

kpieckiel
  • 1
  • 1
  • 2
    scoped_lock is just lock and unlock. You can call try_lock and unlock, or try_lock_for and unlock, if you want. – user253751 Aug 17 '22 at 19:23
  • 4
    If you need to lock multiple mutexes, ensure that you always lock them all in the same order, and make sure that as soon as a lock cannot be obtained that all the locks are released and try again. Don't do any transaction processing until all the locks are obtained. And if possible, do what you can to avoid it. – Eljay Aug 17 '22 at 19:29
  • @user253751, what I'm reading into your comment is that the deadlock avoidance algorithm from std::lock() is nothing fancy. I can simply try_lock_for() on each mutex in series until either I get all the locks or one of them times out (at which point I unlock everything and move on). Is that about it? Is the deadlock avoidance algorithm really that trivial under the hood? – kpieckiel Aug 17 '22 at 19:40
  • @kpieckiel It could be that trivial. It might have a heuristic like trying to lock the hardest lock first. But I daresay it's probably just the trivial one. Go and look in the header file and find out how *your* compiler does it - I believe in you! – user253751 Aug 17 '22 at 20:00
  • 1
    C++17 - Have a read of [`std::scoped_lock`](https://en.cppreference.com/w/cpp/thread/scoped_lock) _"... __deadlock-avoiding__ RAII wrapper for multiple mutexes..."_ _"...The class scoped_lock is a mutex wrapper that provides a convenient RAII-style mechanism for owning one or more mutexes for the duration of a scoped block...."_ – Richard Critten Aug 17 '22 at 20:20
  • @RichardCritten, have a read of my question that I posted above. That function is blocking. In theory, a thread could block forever waiting to lock multiple mutexes. That's the crux of my question... how to **keep a thread from blocking indefinitely** when locking multiple mutexes. – kpieckiel Aug 17 '22 at 20:57
  • @user253751, thanks. Your and Eljay's responses are helpful and pretty much answer my question. – kpieckiel Aug 17 '22 at 20:58
  • Does this answer your question? [Is std::mutex fair?](https://stackoverflow.com/questions/17527621/is-stdmutex-fair) – Richard Critten Aug 17 '22 at 21:16
  • Slightly off-topic, but if you find that your code needs to lock multiple mutexes at once, that's a code-smell.... see if you can redesign your program so that no thread ever needs to hold more than one mutex at a time (and if that's not possible, then try to at least minimize the number of mutexes any thread will need to hold at one time, and enforce a consistent order-of-acquisition in order to avoid deadlocks) – Jeremy Friesner Aug 18 '22 at 02:19
  • @RichardCritten, this is helpful information. Thank you for linking that. – kpieckiel Aug 18 '22 at 12:40
  • @JeremyFriesner, I agree that fewer mutexes is better--and I appreciate the admonition/reminder. In my scenario, I'm self-learning about the concepts of data-oriented design, and when applying it to a multi-threaded program, two pieces of disparate data (i.e., accessed via differing indexes or something) might need to be transformed together. I see no way around locking multiple mutexes other than a giant lock that locks everything. A comment is too small for a code example or more in-depth description, and it's entirely possible I'm doing the DoD wrong. I _am_ still learning DoD. – kpieckiel Aug 18 '22 at 12:47
  • @kpieckiel another approach would be to make each data structure accessible only by a single thread, and then implement thread-safe message queues so that threads can communicate with each other. Then if thread A needs to update a data structure that is only accessible by thread B, thread A can post a message to thread B's message queue, causing thread B to wake up, handle the message, and update the data structure on thread A's behalf. – Jeremy Friesner Aug 18 '22 at 13:47
  • @JeremyFriesner, ooh! I like that approach--definitely worth considering using such a "guard thread." Thanks for that idea! – kpieckiel Aug 19 '22 at 15:22

0 Answers0