Stroustrup states [CPL 4, pg 118]:
The correspondance between the shared data and a
mutexis conventional: the programmer simply has to know whichmutexis supposed to correspond to which data. Obviously, this is error-prone, and equally obviously we try to make the correspondance clear through various language means.
It is better to make the correspondance between the shared data and a mutex explicit rather than implicit, for greater understandability.
Accordingly, I have designed a class to make this correspondance explicit.
In order to name this class, I have used the same convention used by the std::packaged_task class: it packages together a promise and a future and is therefore so called. Accordingly, I have named my class template packaged_mutex<T>, where T is the type of the variable being protected.
There are two possible design approaches for the packaged_mutex<T> class template:
1) As a wrapper for the std::mutex class
2) Extend the std::mutex class
The first approach has the following drawbacks:
1) The members of std::mutex are not directly accessible. I would have to provide an interface for these members.
2) I can't pass a packaged_mutex<T> directly to an std::lock_guard or an std::unique_lock. Only the wrapped std::mutex can be passed to these standard locks.
Both problems are automatically solved by using the second approach. Therefore, the packaged_mutex<T> class template derives publicly from std::mutex and thus is an std::mutex.
Please note that the objective of the packaged_mutex<T> class template is ONLY to explicitly associate an std::mutex with the variable being protected.
The objective is NOT to try out some other method of protecting a variable from concurrent access, such as Atomics.
I have also tried out a variant of the packaged_mutex<T> class template which tries to acquire a lock on the mutex, within the class itself, but it is currently giving compilation errors. Therefore, I am not submitting that version for code-review. The current version leaves locking to the client.
The packaged_mutex<T> class template is presented below:
/** packaged_mutex.h The packaged_mutex<T> class has the following basic functionality: 1) It inherits the mutex class and is therefore a mutex. 2) It includes a variable of type T. This makes the association between the variable and the mutex explicit. **/ #ifndef PACKAGED_MUTEX_BASIC #define PACKAGED_MUTEX_BASIC #include <mutex> /// mutex /// declarations ... /// implementation ... template<typename T> class packaged_mutex : public std::mutex { private: T& var; public: packaged_mutex(T& v) : var {v} { } void setvar(const T& val) { var = val; } T& getvar() { return var; } }; // packaged_mutex #endif /// PACKAGED_MUTEX_BASIC Note:
1) The constructor accepts an lvalue reference to the variable that the mutex is intended to protect. This reference is stored within the class. Thus, the association between the data to be protected and the mutex is made explicit.
2) packaged_mutex<T> provides a member setvar to set the value of the protected variable and a member getvar to retrieve its value.
The following is sample client-code:
/** Test the packaged_mutex<T> class (basic). **/ #include <thread> /// thread #include <mutex> /// lock_guard #include <iostream> /// cout, endl #include "packaged_mutex.h" /// packaged_mutex<T> using namespace std; using pckmtx_char = packaged_mutex<char>; /// variables ... char c {}; pckmtx_char pmtx {c}; /// declarations ... void thread1(); void thread2(); int main() { thread t1 {thread1}; thread t2 {thread2}; t1.join(); t2.join(); } void thread1() { // critical section { lock_guard<pckmtx_char> lg {pmtx}; pmtx.setvar('c'); } // release lock } void thread2() { // critical section { lock_guard<pckmtx_char> lg {pmtx}; cout << "variable has value " << pmtx.getvar() << endl; } // release lock }
Note:
1) Here, we create a packaged_mutex<char> (variable pmtx) which associates a char c with the packaged_mutex<char>:
using pckmtx_char = packaged_mutex<char>; /// variables ... char c {}; pckmtx_char pmtx {c};
2) The program starts two threads t1 and t2.
3) thread t1 contains a lock_guard which acquires the packaged_mutex<char>:
lock_guard<pckmtx_char> lg {pmtx};
4) thread t1 then sets the value of the protected variable:
pmtx.setvar('c');
5) thread t2 also contains a lock_guard which acquires the packaged_mutex<char>. It then accesses the protected variable and prints its value:
cout << "variable has value " << pmtx.getvar() << endl;
6) As expected, the packaged_mutex serializes access to the protected variable and thus avoids a data race.
Comments and suggestions are requested.
valargument is declaredconstso that it might not be modified within thesetvarmethod. However, theprivatevariablevarcan be modified by thesetvarmethod. There's no contradiction there. \$\endgroup\$This method should probably be templated and perfect-forwarding.Can you clarify what you mean, please? Thanks. \$\endgroup\$