I'm trying to understand how to use mutexes with objects in c++. I have the following (trivial) multi-threaded code I'm using as a speed test:
struct Rope{
int n, steps, offset;
//std::mutex mut;
Rope() {}
Rope(int n, int steps, int offset) : n(n), steps(steps), offset(offset) {}
void compute(){
double a[n];
for (int i=0; i<n; i++)
a[i] = i + offset;
for (int step=0; step<steps; step++)
for (int i=0; i<n; i++)
a[i] = sin(a[i]);
}
};
void runTest(){
int numRuns = 30;
int n = 10000;
int steps = 10000;
std::vector<Rope> ropes;
std::vector<std::thread> threads;
for (int i=0; i<numRuns; i++)
ropes.push_back(Rope(n, steps, i));
for (auto& r : ropes)
threads.push_back(std::thread(&Rope::compute, r));
for (std::thread& t : threads)
t.join();
}
The code runs fine as is, and sees a ~4x speedup on my 4 core machine. I am, of course, not storing anything in the Rope, so there is no need for a mutex. If I now presume that I did have some data I needed to protect, I'd like to attach a mutex to the Rope and (e.g.) call std::lock_guard within the compute() loop. If I uncomment the mutex, though, I get a bunch of compiler errors about "use of deleted function" for assignment and copy operators. What am I missing in my goal to safely lock an object?
The straightforward way to make a class threadsafe is to add a mutex attribute and lock the mutex in the accessor methods
class cMyClass {
boost::mutex myMutex;
cSomeClass A;
public:
cSomeClass getA() {
boost::mutex::scoped_lock lock( myMutex );
return A;
}
};
The problem is that this makes the class non-copyable. This is important, especially if you want to store objects of the class in a container.
I can make things work by making the mutex a static.
class cMyClass {
static boost::mutex myMutex;
cSomeClass A;
public:
cSomeClass getA() {
boost::mutex::scoped_lock lock( myMutex );
return A;
}
};
However, this means that every instance of the class blocks when any other instance is being accessed, because they all share the same mutex.
Theoretically, a class containing a non-static mutex can be made copyable by hand coding the copy constructor and assignment operator so as to leave out the mutex. However, this is difficult and tedious to do properly, especially for a class with a large number of attributes that frequently change during development.
If a static mutex that blocks access to all instances of the class when one is blocked is not acceptable, then the best and simplest approach is to maintain the mutexes outside the class. It might seem unfortunate to expose the inner workings of a class in this way, but the alternatives are way more complex, hence unreliable, and I frequently find significant optimizations when the mutexes are handled at the level of the code accessing the class.
You're missing the fact that mutex
isn't copyable. What would it mean
to make a copy of a mutex? The place to acquire and release the mutex
is in Rope::compute
, and since all of the threads have to access the
same mutex, you'll have to define it in runTest
and pass it in by
reference or by pointer.