-
Notifications
You must be signed in to change notification settings - Fork 184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce a MutexProtected helper class for multithreading #2778
Comments
Conceptually it makes sense and I like it. But I suspect there are cases where greater modification of existing code would be necessary to best make use of it. Many multi-threading applications will currently do something like: class Master {
protected:
vector<float> read_by_threads;
float written_from_threads;
std::mutex mutex;
}
class Slave {
public:
Slave (Master& master) :
master (master),
local_variable (0.0) { }
Slave (const Slave& that) :
master (that.master),
local_variable (0.0) { }
~Slave() {
std::lock_guard<std::mutex> lock (master.mutex);
written_from_threads += local_variable;
}
void operator() () {
// Does something with master.read_by_threads
local_variable += X;
}
protected:
Master& master;
float local_variable;
}
I'd rather have something like: class Slave::WriteBack {
public:
WriteBack (Master& master) :
written_from_threads (0.0) { }
WriteBack (const Shared&) = delete;
~WriteBack() {
master.written_from_threads = written_from_threads;
}
void operator() (const Slave& that) {
std::lock_guard<std::mutex> lock (mutex);
written_from_threads += that.written_from_threads;
}
protected:
Master& master;
float written_from_threads;
std::mutex mutex;
}
class Slave {
public:
Slave (Master& master) :
master (master),
write_back (new WriteBack (master)) { }
Slave (const Slave& that) :
master (that.master),
write_back (that.write_back),
local_variable (0.0) { }
~Slave() {
*write_back += local_variable;
}
void operator() () {
// Does something with master.read_by_threads
local_variable += X;
}
protected:
const Master& master;
std::shared_ptr<WriteBack> write_back;
float local_variable;
} Crucially, your In addition:
, all of which are I think beneficial. It might be a case of grepping for mutexes and assessing the proportion of those for which such a wrapper is trivially applicable. Others might require structural changes to facilitate it. |
👍 All for this type of abstraction, especially if it helps avoid issues down the track. Quick thought though: it's very rare for developers to resort to manually handling a mutex - they'll typically rely on the On a slightly different note: Rob, where do you see the type of construct you mentioned? We have quite a few cases of a single Shared class being referenced by a bunch of worker classe, which update some value from the shared class in their destructor, but there is normally no need to mutex-protect that final update, as that's guaranteed to happen in the main thread anyway (at least it is when using our constructs). The order of execution when running a ThreadedLoop is:
Since all destructors are invoked in the main threads, once all worker threads have joined, there is no scope for race conditions, and no need to protect the operation with a mutex. You may of course have other bits of code that operate slightly differently, but if so, it might be worth double-checking whether they really need that mutex in the final update, assuming each worker thread genuinely does not touch any shared variable during the execution of its own |
Yes, I agree. I would add that low level constructs like a mutex are not only not needed, but also fundamentally undesirable in application level code. Using higher level abstractions like queues is a much saner way of doing multithreading. |
Mostly I was thinking of SIFT2, since that's where I've invested time most recently. Statistical inference code does something similar also, and it's a simpler example to use here. Question is whether this lock is required. |
Closed by e8232b2 |
In C++, it's very easy to forget to lock a mutex before accessing the resource it protects. An example of this situation in our codebase is illustrated in #2755.
This "forgot to lock the mutex" problem can dealt with rather effectively by introducing a wrapper
MutexProtected
class that protects a resource that needs synchronisation across threads. Suppose you have code that looks like this:Then we can transform it into:
The implementation of
MutexProtected
can be something like this:Using this class to protect synchronised values, we can ensure that a developer never forgets to lock a mutex and reducing the chances of a race condition. This pattern is somewhat similar to how mutexes are used in Rust and is available in number of C++ libraries like Facebook's Folly or Boost. I would like to add to this our codebase to help prevent race conditions.
The text was updated successfully, but these errors were encountered: