operator Overloading and mutex - c++

I want to create mutex(boost) inside the operator (=)
the code below is thread safe?
ub_int & operator=(const int x)
{
mutex::scoped_lock mylock(MX_ub, defer_lock);
mylock.lock();
this->v=x;
mylock.unlock();
return *this;
}

The code seems to be correct, but
as described in the comments, you should not use the defer_lock parameter
At least in the example you probably don't need a mutex at all, as a write to an int is usually an atomic operation

Related

Best practices for move constructor of a class with mutex-locked caching

I sometimes have classes like this:
class HasMutexLockedCache {
private:
SomeType m_regularData;
mutable std::mutex m_mutex;
mutable std::optaional<T> m_cache;
// Etc.
public:
const m_regularData& getData() const { return m_regularData; }
const T& getExpensiveResult() const {
std::scoped_lock lock(m_mutex);
if (!m_cache) {
m_cache = expensiveFunction();
}
return m_cache;
}
HasMutexLockedCache(const HasMutexLockedCache& other)
: m_regularData(other.m_regularData)
{
std::scoped_lock lock(other.m_mutex);
// I figure we don't have to lock this->m_mutex because
// we are in the c'tor and so nobody else could possibly
// be messing with m_cache.
m_cache = other.m_cache;
}
HasMutexLockedCache(HasMutexLockedCache&& other)
: m_regularData(std::move(other.m_regularData))
{
// What here?
}
HasMutexLockedCache& operator=(HasMutexLockedCache&& other) {
m_regularData = std::move(other.m_regularData);
// Bonus points: What here? Lock both mutexes? One?
// Only lock this->m_mutex depending on how we
// document thread safety?
}
};
My question: what goes in HasMutexLockedCache(HasMutexLockedCache&& other) (and likewise in HasMutexLockedCache& operator=(HasMutexLockedCache&& other)? I think we don't need to lock other.m_mutex because, for other to be an rvalue reference, we know nobody else can see it, just like we don't have to lock this->m_mutex in the c'tor. However, I'd like some guidance. What are the best practices here? Should we be locking other.m_mutex?
You have to remember that l-value objects can be moved using std::move so we do need to lock them too:
HasMutexLockedCache(HasMutexLockedCache&& other) {
std::scoped_lock lock(other.m_mutex);
m_cache = std::move(other.m_cache);
}
HasMutexLockedCache& operator=(HasMutexLockedCache&& other) {
std::scoped_lock lock(m_mutex, other.m_mutex);
m_cache = std::move(other.m_cache);
return *this;
}
I'd like some guidance. What are the best practices here? Should we be locking other.m_mutex?
#Galik's answer explains how one could implement a move-constructor for this, however you should consider whether this is a safe and coherent idea for your abstraction.
If an object contains a std::mutex, generally this means that it may have concurrent accesses at different times which warrant this. If this is the case, then move-semantics can be quite difficult to work with when faced with multi-threading -- since you may have thread A move the contents of m_cache before thread B accesses it, resulting in it reading a moved-from state (which, depending on what the state is being checked, may not be well-defined). These types of errors can be quite complicated to debug, and even harder to reproduce!
Often if you have a type like this, it would be better to be sharing this type across threads explicitly, either with a shared lifetime via shared_ptr, or some form of synchronization from outside so each thread cannot destructively interfere with one another.

C++ thread-safe bracket operator proxy

Given a simple wrapper around a standard vector, what is a good way to implement operator[] in a thread-safe way in order to be able to set the content as usual?
struct bracket_operator_proxy;
struct example
{
auto operator[](size_t i) const { return bracket_operator_proxy(v, m, i); }
private:
std::vector<double> v;
std::mutex m;
};
Here is my quick and naive attempt for bracket_operator_proxy:
struct bracket_operator_proxy
{
bracket_operator_proxy(std::vector<double>& v, std::mutex& m, int i)
: v(v), m(m), i(i) {}
operator double() const
{
std::lock_guard<std::mutex> l(m);
return v[i];
}
auto operator=(double d)
{
std::lock_guard<std::mutex> l(m);
v[i] = d;
return d;
}
//... further assignment operators ...
private:
std::vector<double>& v;
std::mutex& m;
int i;
};
Is this already enough? Or am I missing something which will blow my leg off?
Once you have operator-> (which is very useful) you'll need to return a -> proxy which extends lock lifetime until end of statement, and exposes you to single threaded deadlock.
Look at thread safe monads/functor/wrapper like the one here. It doesn't make the locks completely transparent, but they should not be.
Do not share data between threads
If you share data, make it immutable
If it must be mutated, isolate access through a bottleneck of known safe design. A message queue say.
If you cannot do that, consider redesign
Really. Atomic maybe?
Have a limited set of functions that manage locks explicitly
Ok, now wrap in reader/writer monad as above, with easy-ish compound operations
Make code that magically gets locks and looks just like non-thread interacting code, thus lulling your readers into a false sense of security and efficiency
In decreasing preference.
The dangerous and hard part of thread safety is not the fact that the syntax is awkward. It is that lock based thread safety is nearly impossible to prove correct and safe. Making the syntax easier to use is not a high value goal.
As an example, v[i]=v[i+1] is fraught with a lack of synchronization: between the read and the write anything could have changed. Let alone the problem of "is i a valid index?"

Implementing swap for class with std::mutex

Suppose we have a class with a std::mutex:
class Foo
{
std::mutex mutex_;
std::string str_;
// other members etc
public:
friend void swap(Foo& lhs, Foo& rhs) noexcept;
}
What is the appropriate way to implement the swap method here? Is it required/safe to lock each mutex separately and then swap everything? e.g.
void swap(Foo& lhs, Foo& rhs) noexcept
{
using std::swap;
std::lock_guard<std::mutex> lock_lhs {lhs.mutex_}, lock_rhs {rhs.mutex_};
swap(ls.str_, rhs.str_);
// swap everything else
}
I've seen that in C++17, std::lock_guard will have a constructor taking multiple mutexes for avoiding deadlock, but I'm not sure if that's an issue here?
You can use std::lock() to acquire the locks in a non-deadlocking way.
If you want to use std::lock_guard, have them adopt the locks once taken:
std::lock(lhs.mutex_, rhs.mutex_);
std::lock_guard<std::mutex> lock_a(lhs.mutex_, std::adopt_lock);
std::lock_guard<std::mutex> lock_b(rhs.mutex_, std::adopt_lock);
//swap actions
swap(ls.str_, rhs.str_);
If you prefer std::unique_lock, then construct them without locking, then call std::lock() to lock them both (this also works with std::lock_guard):
std::unique_lock<std::mutex> lock_a(lhs.mutex_, std::defer_lock);
std::unique_lock<std::mutex> lock_b(rhs.mutex_, std::defer_lock);
std::lock(lock_a, lock_b);
//swap actions
swap(ls.str_, rhs.str_);
In both cases, you should first test for lhs and rhs being the same object, because using std::lock with one mutex twice is undefined behavior:
if (&lhs == &rhs)
return;
I don't think your swap implementation is safe. If another algorithm tries to lock rhs.mutex_ first and then lhs.mutex_, you may end up with a deadlock. Try std::lock() instead.

How should I deal with mutexes in movable types in C++?

By design, std::mutex is not movable nor copyable. This means that a class A holding a mutex won't receive a default move constructor.
How would I make this type A movable in a thread-safe way?
Let's start with a bit of code:
class A
{
using MutexType = std::mutex;
using ReadLock = std::unique_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
mutable MutexType mut_;
std::string field1_;
std::string field2_;
public:
...
I've put some rather suggestive type aliases in there that we won't really take advantage of in C++11, but become much more useful in C++14. Be patient, we'll get there.
Your question boils down to:
How do I write the move constructor and move assignment operator for this class?
We'll start with the move constructor.
Move Constructor
Note that the member mutex has been made mutable. Strictly speaking this isn't necessary for the move members, but I'm assuming you also want copy members. If that is not the case, there is no need to make the mutex mutable.
When constructing A, you do not need to lock this->mut_. But you do need to lock the mut_ of the object you're constructing from (move or copy). This can be done like so:
A(A&& a)
{
WriteLock rhs_lk(a.mut_);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
Note that we had to default construct the members of this first, and then assign them values only after a.mut_ is locked.
Move Assignment
The move assignment operator is substantially more complicated because you do not know if some other thread is accessing either the lhs or rhs of the assignment expression. And in general, you need to guard against the following scenario:
// Thread 1
x = std::move(y);
// Thread 2
y = std::move(x);
Here is the move assignment operator that correctly guards the above scenario:
A& operator=(A&& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
WriteLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = std::move(a.field1_);
field2_ = std::move(a.field2_);
}
return *this;
}
Note that one must use std::lock(m1, m2) to lock the two mutexes, instead of just locking them one after the other. If you lock them one after the other, then when two threads assign two objects in opposite order as shown above, you can get a deadlock. The point of std::lock is to avoid that deadlock.
Copy Constructor
You didn't ask about the copy members, but we might as well talk about them now (if not you, somebody will need them).
A(const A& a)
{
ReadLock rhs_lk(a.mut_);
field1_ = a.field1_;
field2_ = a.field2_;
}
The copy constructor looks much like the move constructor except the ReadLock alias is used instead of the WriteLock. Currently these both alias std::unique_lock<std::mutex> and so it doesn't really make any difference.
But in C++14, you will have the option of saying this:
using MutexType = std::shared_timed_mutex;
using ReadLock = std::shared_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
This may be an optimization, but not definitely. You will have to measure to determine if it is. But with this change, one can copy construct from the same rhs in multiple threads simultaneously. The C++11 solution forces you to make such threads sequential, even though the rhs isn't being modified.
Copy Assignment
For completeness, here is the copy assignment operator, which should be fairly self explanatory after reading about everything else:
A& operator=(const A& a)
{
if (this != &a)
{
WriteLock lhs_lk(mut_, std::defer_lock);
ReadLock rhs_lk(a.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1_ = a.field1_;
field2_ = a.field2_;
}
return *this;
}
And etc.
Any other members or free functions that access A's state will also need to be protected if you expect multiple threads to be able to call them at once. For example, here's swap:
friend void swap(A& x, A& y)
{
if (&x != &y)
{
WriteLock lhs_lk(x.mut_, std::defer_lock);
WriteLock rhs_lk(y.mut_, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
using std::swap;
swap(x.field1_, y.field1_);
swap(x.field2_, y.field2_);
}
}
Note that if you just depend on std::swap doing the job, the locking will be at the wrong granularity, locking and unlocking between the three moves that std::swap would internally perform.
Indeed, thinking about swap can give you insight into the API you might need to provide for a "thread-safe" A, which in general will be different from a "non-thread-safe" API, because of the "locking granularity" issue.
Also note the need to protect against "self-swap". "self-swap" should be a no-op. Without the self-check one would recursively lock the same mutex. This could also be solved without the self-check by using std::recursive_mutex for MutexType.
Update
In the comments below Yakk is pretty unhappy about having to default construct things in the copy and move constructors (and he has a point). Should you feel strongly enough about this issue, so much so that you are willing to spend memory on it, you can avoid it like so:
Add whatever lock types you need as data members. These members must come before the data that is being protected:
mutable MutexType mut_;
ReadLock read_lock_;
WriteLock write_lock_;
// ... other data members ...
And then in the constructors (e.g. the copy constructor) do this:
A(const A& a)
: read_lock_(a.mut_)
, field1_(a.field1_)
, field2_(a.field2_)
{
read_lock_.unlock();
}
Oops, Yakk erased his comment before I had the chance to complete this update. But he deserves credit for pushing this issue, and getting a solution into this answer.
Update 2
And dyp came up with this good suggestion:
A(const A& a)
: A(a, ReadLock(a.mut_))
{}
private:
A(const A& a, ReadLock rhs_lk)
: field1_(a.field1_)
, field2_(a.field2_)
{}
Given there doesn't seem to be a nice, clean, easy way to answer this - Anton's solution I think is correct but its definitely debatable, unless a better answer comes up I would recommend putting such a class on the heap and looking after it via a std::unique_ptr:
auto a = std::make_unique<A>();
Its now a fully movable type and anyone who has a lock on the internal mutex whilst a move happens is still safe, even if its debatable whether this is a good thing to do
If you need copy semantics just use
auto a2 = std::make_shared<A>();
This is an upside-down answer. Instead of embedding "this objects needs to be synchronized" as a base of the type, instead inject it under any type.
You deal with a synchronized object very differently. One big issue is you have to worry about deadlocks (locking multiple objects). It also should basically never be your "default version of an object": synchronized objects are for objects that will be in contention, and your goal should be to minimize contention between threads, not sweep it under the rug.
But synchronizing objects is still useful. Instead of inheriting from a synchronizer, we can write a class that wraps an arbitrary type in synchronization. Users have to jump through a few hoops to do operations on the object now that it is synchronized, but they are not limited to some hand-coded limited set of operations on the object. They can compose multiple operations on the object into one, or have an operation on multiple objects.
Here is a synchronized wrapper around an arbitrary type T:
template<class T>
struct synchronized {
template<class F>
auto read(F&& f) const&->std::result_of_t<F(T const&)> {
return access(std::forward<F>(f), *this);
}
template<class F>
auto read(F&& f) &&->std::result_of_t<F(T&&)> {
return access(std::forward<F>(f), std::move(*this));
}
template<class F>
auto write(F&& f)->std::result_of_t<F(T&)> {
return access(std::forward<F>(f), *this);
}
// uses `const` ness of Syncs to determine access:
template<class F, class... Syncs>
friend auto access( F&& f, Syncs&&... syncs )->
std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
{
return access2( std::index_sequence_for<Syncs...>{}, std::forward<F>(f), std::forward<Syncs>(syncs)... );
};
synchronized(synchronized const& o):t(o.read([](T const&o){return o;})){}
synchronized(synchronized && o):t(std::move(o).read([](T&&o){return std::move(o);})){}
// special member functions:
synchronized( T & o ):t(o) {}
synchronized( T const& o ):t(o) {}
synchronized( T && o ):t(std::move(o)) {}
synchronized( T const&& o ):t(std::move(o)) {}
synchronized& operator=(T const& o) {
write([&](T& t){
t=o;
});
return *this;
}
synchronized& operator=(T && o) {
write([&](T& t){
t=std::move(o);
});
return *this;
}
private:
template<class X, class S>
static auto smart_lock(S const& s) {
return std::shared_lock< std::shared_timed_mutex >(s.m, X{});
}
template<class X, class S>
static auto smart_lock(S& s) {
return std::unique_lock< std::shared_timed_mutex >(s.m, X{});
}
template<class L>
static void lock(L& lockable) {
lockable.lock();
}
template<class...Ls>
static void lock(Ls&... lockable) {
std::lock( lockable... );
}
template<size_t...Is, class F, class...Syncs>
friend auto access2( std::index_sequence<Is...>, F&&f, Syncs&&...syncs)->
std::result_of_t< F(decltype(std::forward<Syncs>(syncs).t)...) >
{
auto locks = std::make_tuple( smart_lock<std::defer_lock_t>(syncs)... );
lock( std::get<Is>(locks)... );
return std::forward<F>(f)(std::forward<Syncs>(syncs).t ...);
}
mutable std::shared_timed_mutex m;
T t;
};
template<class T>
synchronized< T > sync( T&& t ) {
return {std::forward<T>(t)};
}
C++14 and C++1z features included.
this assumes that const operations are multiple-reader safe (which is what std containers assume).
Use looks like:
synchronized<int> x = 7;
x.read([&](auto&& v){
std::cout << v << '\n';
});
for an int with synchronized access.
I'd advise against having synchronized(synchronized const&). It is rarely needed.
If you need synchronized(synchronized const&), I'd be tempted to replace T t; with std::aligned_storage, allowing manual placement construction, and do manual destruction. That allows proper lifetime management.
Barring that, we could copy the source T, then read from it:
synchronized(synchronized const& o):
t(o.read(
[](T const&o){return o;})
)
{}
synchronized(synchronized && o):
t(std::move(o).read(
[](T&&o){return std::move(o);})
)
{}
for assignment:
synchronized& operator=(synchronized const& o) {
access([](T& lhs, T const& rhs){
lhs = rhs;
}, *this, o);
return *this;
}
synchronized& operator=(synchronized && o) {
access([](T& lhs, T&& rhs){
lhs = std::move(rhs);
}, *this, std::move(o));
return *this;
}
friend void swap(synchronized& lhs, synchronized& rhs) {
access([](T& lhs, T& rhs){
using std::swap;
swap(lhs, rhs);
}, *this, o);
}
the placement and aligned storage versions are a bit messier. Most access to t would be replaced by a member function T&t() and T const&t()const, except at construction where you'd have to jump through some hoops.
By making synchronized a wrapper instead of part of the class, all we have to ensure is that the class internally respects const as being multiple-reader, and write it in a single-threaded manner.
In the rare cases we need a synchronized instance, we jump through hoops like the above.
Apologies for any typos in the above. There are probably some.
A side benefit to the above is that n-ary arbitrary operations on synchronized objects (of the same type) work together, without having to hard-code it before hand. Add in a friend declaration and n-ary synchronized objects of multiple types might work together. I might have to move access out of being an inline friend to deal with overload conficts in that case.
live example
First of all, there must be something wrong with your design if you want to move an object containing a mutex.
But if you decide to do it anyway, you have to create a new mutex in move constructor, that is e.g:
// movable
struct B{};
class A {
B b;
std::mutex m;
public:
A(A&& a)
: b(std::move(a.b))
// m is default-initialized.
{
}
};
This is thread-safe, because the move constructor can safely assume that its argument isn't used anywhere else, so the locking of the argument isn't required.
Using mutexes and C++ move semantics is an excellent way to safely and efficiently transfer data between threads.
Imagine a 'producer' thread that makes batches of strings and provides them to (one or more) consumers. Those batches could be represented by an object containing (potentially large) std::vector<std::string> objects.
We absolutely want to 'move' the internal state of those vectors into their consumers without unnecessary duplication.
You simply recognize the mutex as part of the object not part of the object's state. That is, you don't want to move the mutex.
What locking you need depends on your algorithm or how generalized your objects are and what range of uses you permit.
If you only ever move from a shared state 'producer' object to a thread-local 'consuming' object you might be OK to only lock the moved from object.
If it's a more general design you will need to lock both. In such a case you need to then consider dead-locking.
If that is a potential issue then use std::lock() to acquire locks on both mutexes in a deadlock free way.
http://en.cppreference.com/w/cpp/thread/lock
As a final note you need to make sure you understand the move semantics.
Recall that the moved from object is left in a valid but unknown state.
It's entirely possible that a thread not performing the move has a valid reason to attempt access the moved from object when it may find that valid but unknown state.
Again my producer is just banging out strings and the consumer is taking away the whole load. In that case every time the producer tries to add to the vector it may find the vector non-empty or empty.
In short if the potential concurrent access to the moved from object amounts to a write it's likely to be OK. If it amounts to a read then think about why it's OK to read an arbitrary state.

Assignment operator in struct after adding mutex in C++

I had a struct type:
struct MyStruct {
int field1;
int field2;
}
Then it became necessary to add a mutex to it to make it shared between threads:
struct MyStruct {
std::mutex _mutex;
int field1;
int field2;
}
But then the compiler (clang) give me these messages on lines where I assign one existing struct to variable of type MyStruct like MyStruct mystruct = p_MyStructMap->at(clientId);:
(1) error: object of type 'MyStruct' cannot be assigned because its copy assignment operator is implicitly deleted
(2) note: copy assignment operator of 'MyStruct' is implicitly deleted because field '_mutex' has a deleted copy assignment operator
std::mutex _mutex
^
(3) /usr/bin/../lib/gcc/x86_64-linux-gnu/4.8/../../../../include/c++/4.8/mutex:129:12: note: function has been explicitly marked deleted here
mutex& operator=(const mutex&) = delete;
^
Please, help: How can I rewrite struct or may be the program logic to use mutexes for this struct?
Assuming that _mutex was added to protect the other fields from being simultaneously accessed, you will need to lock the mutex(s) during assignment, unless you can otherwise guarantee that multiple threads aren't accessing either side of the assignment expression:
x = y;
If any other thread is reading or writing to x simultaneously, you have a race without locking.
If any other thread is writing to y simultaneously, you have a race without locking.
If you do indeed need to lock, it is not quite as simple as locking both sides:
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
// WRONG! DO NOT DO THIS!!!
std::lock_guard<std::mutex> lhs_lk(_mutex);
std::lock_guard<std::mutex> rhs_lk(o._mutex);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
The reason you should not do this is imagine thread A does this:
x = y;
And simultaneously thread B does this:
y = x;
Now you have the potential for deadlock. Imagine thread A locks x._mutex, and then thread B locks y._mutex. Now both thread A and B will block trying to lock their o._mutex, and neither thread will ever succeed because it is waiting for the other thread to release it.
The correct formulation of the assignment operator can look like this:
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
std::lock(_mutex, o._mutex);
std::lock_guard<std::mutex> lhs_lk(_mutex, std::adopt_lock);
std::lock_guard<std::mutex> rhs_lk(o._mutex, std::adopt_lock);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
The job of std::lock(m1, m2, ...) is to lock all of the mutexes in some magical way that does not deadlock. For more detail than you probably want to know about how that is done, you can read Dining Philosophers Rebooted.
Now with _mutex and o._mutex locked, you simply need to make their unlocking exception safe by having the lock_guards adopt ownership of their mutexes. That is, they will no longer try to lock their mutex on construction, but they will still unlock them on destruction. The lock_guard constructors themselves throw nothing, so this is all exception safe.
Oh, you will also have to store _mutex as a mutable data member else you won't be able to lock and unlock it on the rhs.
In C++14 a potential optimization is available if you want to try it: You can "write-lock" this->_mutex and "read-lock" o._mutex. This would allow multiple threads to simultaneously assign from a common rhs if no threads are assigning to that rhs. In order to do that you need to have MyStruct store a std::shared_timed_mutex instead of a std::mutex:
#include <mutex>
#include <shared_mutex>
struct MyStruct
{
using MutexType = std::shared_timed_mutex;
using ReadLock = std::shared_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
mutable MutexType _mutex;
int field1;
int field2;
MyStruct& operator=(const MyStruct& o)
{
if (this != &o)
{
WriteLock lhs_lk(_mutex, std::defer_lock);
ReadLock rhs_lk(o._mutex, std::defer_lock);
std::lock(lhs_lk, rhs_lk);
field1 = o.field1;
field2 = o.field2;
}
return *this;
}
};
This is similar to as before except that we need to change the type of the mutex, and now the lhs locks with unique_lock (which write-locks the lhs mutex) and the rhs locks with shared_lock (which read-locks the rhs mutex). Here we also use std::defer_lock to construct the locks but to tell the locks that the mutex isn't locked yet, and don't lock on construction. And then our old friend std::lock(m1, m2) is used to tell both locks to lock at the same time without deadlock. Yes, std::lock works with both mutex and lock types. Anything that has member lock(), try_lock(), and unlock(), will work with std::lock(m1, m2, ...).
Note, the C++14 technique isn't definitely an optimization. You will have to measure to confirm or deny that it is. For something as simple as MyStruct it probably isn't an optimization, except perhaps for some special usage patterns. The C++11 technique with std::mutex continues to be a valuable tool in the toolbox, even in C++14.
For ease in switching back and fourth between the mutex and shared_timed_mutex, this latest example makes use of type aliases, which can be easily changed. One only has to change two lines to switch back to mutex:
using MutexType = std::shared_timed_mutex;
using ReadLock = std::sharedunique_lock<MutexType>;
using WriteLock = std::unique_lock<MutexType>;
std::mutex variables/members cannot be assigned, therefore, you get the error.
Try something like this:
struct MyStruct {
std::mutex _mutex;
int field1;
int field2;
MyStruct &operator=(const MyStruct &o) {
field1=o.field1;
field2=o.field2;
return *this;
}
};
However, this will not lock the mutex during the assignment. Depending on the context you may need to add locking inside the assignment operator or in the invoking function.