Reputation: 23
I am trying to create a physics engine (just for fun) and I would like it to be multi-threaded.
I understand the basics of a mutex (only one thread can modify the resources it is guarding at a time, they should be at a class level instead of thread level, etc). I would prefer to NOT use atomics for the member variables (so that if I am doing complex operations on them, they will not be changed right in the middle of the execution), or to simply copy the variables (for a hopefully lower memory footprint).
Following this notion, a (simplified) vector class might look like this:
class vector
{
float x_, y_;
std::mutex guard_;
};
If I want to use it, how should they be locked?
void foo(vector v1, vector v2)
{
std::lock_guard<std::mutex>(v1.guard_);
std::lock_guard<std::mutex>(v2.guard_);
// Do stuff with v1 and v2...
}
Like this? Will this actually protect both both objects?
TL;DR How should mutexes be locked when there are multiple objects being operated on by the same thread? (Without using atomics or making copies)
Upvotes: 2
Views: 1129
Reputation: 39818
What you wrote is fine, with one infamous caveat: you will deadlock if two threads try to take the same two locks but in opposite order. This is illustrated by the classic dining philosophers problem.
The standard solution is to impose some fixed, arbitrary order on the locks and take the “lower” one first in all cases. user3290797’s answer provides the correct library facilities to use to do this.
Upvotes: 4
Reputation: 3203
std::lock_guard<std::mutex>(v1.guard_); std::lock_guard<std::mutex>(v2.guard_);
This way there is a risk of a deadlock if another thread tries to lock the same two mutexes in a different order.
In order to both avoid deadlocks and ensure exception safety, you need to first lock both mutexes at once, and then pass the already locked mutexes to lock_guard
's adopting constructors:
std::lock(v1.guard_, v2.guard_);
std::lock_guard<std::mutex> guard1(v1.guard_, std::adopt_lock);
std::lock_guard<std::mutex> guard2(v2.guard_, std::adopt_lock);
In C++ 17 you can use std::scoped_lock
, a class analogous to std::lock_guard
, but capable of owning multiple mutexes:
std::scoped_lock guard{v1.guard_, v2.guard_};
Upvotes: 6