Stroustrup states [CPL 4, pg 118]:
The correspondance between the shared data and a
mutex
is conventional: the programmer simply has to know whichmutex
is 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.
val
argument is declaredconst
so that it might not be modified within thesetvar
method. However, theprivate
variablevar
can be modified by thesetvar
method. There's no contradiction there. \$\endgroup\$This method should probably be templated and perfect-forwarding.
Can you clarify what you mean, please? Thanks. \$\endgroup\$