Given the following example code:
int var;
int mvar;
std::mutex mvar_mutex;
void f(){
mvar_mutex.lock();
mvar = var * var;
mvar_mutex.unlock();
}
I want to express that mvar_mutex is bound to the variable mvar and protects only that variable. mvar_mutex should not protect var because it is not bound to it. Hence the compiler would be allowed to transform the above code into the below code:
int var;
int mvar;
std::mutex mvar_mutex;
void f(){
int r = var * var; //possible data race created if binding is not known
mvar_mutex.lock();
mvar = r;
mvar_mutex.unlock();
}
This might reduce contention on the lock as less work is being done while holding it.
For int this can be done using std::atomic<int> mvar; and removing mvar_mutex, but for other types such as std::vector<int> this is not possible.
How do I express the mutex-variable binding in a way that C++ compilers understand it and do the optimization? It should be allowed to reorder any variable up or down across mutex boundaries for any variable that is not bound to that mutex
Since the code is being generated using clang::ASTConsumer and clang::RecursiveASTVisitor I am willing to use non-standard extensions and AST manipulations as long as clang (ideally clang 4.0) supports them and the resulting code does not need to be elegant or human-readable.
Edit since this seems to be causing confusion: The above transformation is not legal in C++. The described binding of mutex to variable doesn't exist. The question is about how to implement that or achieve the same effect.
If you wish to achieve that the std::mutex will only be held until an operation is performed on the protected object, you can write a wrapper class as follows:
#include <cstdio>
#include <mutex>
template<typename T>
class LockAssignable {
public:
LockAssignable& operator=(const T& t) {
std::lock_guard<std::mutex> lk(m_mutex);
m_protected = t;
return *this;
}
operator T() const {
std::lock_guard<std::mutex> lk(m_mutex);
return m_protected;
}
/* other stuff */
private:
mutable std::mutex m_mutex;
T m_protected {};
};
inline int factorial(int n) {
return (n > 1 ? n * factorial(n - 1) : 1);
}
int main() {
int var = 5;
LockAssignable<int> mvar;
mvar = factorial(var);
printf("Result: %d\n", static_cast<int>(mvar));
return 0;
}
In the example above the factorial will be calculated in advance and the m_mutex will be acquired only when the assignment or the implicit conversion operator being called on mvar.
Assembly Output
For the primitive data types you can use std::atomic with std::memory_order_relaxed.
The documentation states that:
there are no synchronization or ordering constraints imposed on other
reads or writes, only this operation's atomicity is guaranteed
In the following example, the atomicity of the assignation is guaranteed, but the compiler should be able to move the operations.
std::atomic<int> z = {0};
int a = 3;
z.store(a*a, std::memory_order_relaxed);
For objects, I thought of several solutions, but:
There is no standard way to remove ordering requirements from std::mutex.
It is not possible to create a std::atomic<std::vector>.
It is not possible to create a spinlock using std::memory_order_relaxed (see the example).
I have found some answers that state that:
If the function is not visible in the compilation unit, the compiler generates a barrier because it does not know which variables it uses.
If the function is visible and there is a mutex, the compiler generates a barrier.
For example, see this and this
So, in order to express that mvar_mutex is bound to the variable, you can use some classes as stated by the other answers but I do not think it is possible to fully allow the reordering of the code.
I want to express that mvar_mutex is bound to the variable mvar and protects only that variable.
You can't do this. A mutex actually guards the critical region of machine instructons between the acquisition and release. Only by convention is that associated with a particular instance of shared data.
To avoid doing unnecessary steps inside the critical region, keep the critical regions as simple as possible. In a critical region, only with local variables which the compiler can "see" are obviously not shared with other threads, and with one set of shared data belonging to that mutex. Try not to access other data in the critical region that might be suspected of being shared.
If you could have your proposed language feature, it would only introduce the possibility of error into a program. All it does is take code which is now correct, and make some of it incorrect (in exchange for the promise of some speed: that some code stays correct and is faster, because extraneous computations are moved out of the critical region).
It's like taking a language which already has a nice order of evaluation, in which a[i] = i++ is well defined, and screwing it up with unspecified evaluation order.
How about a locked var template ?
template<typename Type, typename Mutex = std::mutex>
class Lockable
{
public:
Lockable(_Type t) : var_(std::move(t));
Lockable(_Type&&) = default;
// ... could need a bit more
T operator = (const T& x)
{
std::lock_guard<Lockable> lock(*this);
var_ = x;
return x;
}
T operator *() const
{
std::lock_guard<Lockable> lock(*this);
return var_;
}
void lock() const { const_cast<Lockable*>(this)->mutex_.lock(); }
void unlock() const { const_cast<Lockable*>(this)->mutex_.unlock().; }
private:
Mutex mutex_;
Type var_;
};
locked by assignment operator
Lockable<int>var;
var = mylongComputation();
Works great with lock_guard
Lockable<int>var;
std::lock_guard<Lockable<int>> lock(var);
var = 3;
Practical on containers
Lockable<std::vector<int>> vec;
etc...
You can use folly::Synchronized to make sure that the variable is only accessed under a lock:
int var;
folly::Synchronized<int> vmar;
void f() {
*mvar.wlock() = var * var;
}
I want to express that mvar_mutex is bound to the variable mvar and
protects only that variable.
This is not how a mutex works. It doesn't "bind" to anything in order to protect it. You are still free to access this object directly, in complete disregard with any sort of thread safety whatsoever.
What you should do is hide away the "protected variable" so that it is not directly accessible at all, and write an interface that manipulates it that goes through the mutex. This way you ensure that access to the underlying data is protected by that mutex. It can be a single object, it can be a functional group of objects, it can be a collection of many objects, mutexes and atomics, designed to minimize blocking.
Related
In general, it's not thread-safe to access the same instance of std::map from different threads.
But is it could be thread-safe under such a condition:
no more element would be added to\remove from the instance of std::map after it has been initialized
the type of values of the std::map is std::atomic<T>
Here is the demo code:
#include<atomic>
#include<thread>
#include<map>
#include<vector>
#include<iostream>
class Demo{
public:
Demo()
{
mp_.insert(std::make_pair(1, true));
mp_.insert(std::make_pair(2, true));
mp_.insert(std::make_pair(3, true));
}
int Get(const int& integer, bool& flag)
{
const auto itr = mp_.find(integer);
if( itr == mp_.end())
{
return -1;
}
else
{
flag = itr->second;
return 0;
}
}
int Set(const int& integer, const bool& flag)
{
const auto itr = mp_.find(integer);
if( itr == mp_.end())
{
return -1;
}
else
{
itr->second = flag;
return 0;
}
}
private:
std::map<int, std::atomic<bool>> mp_;
};
int main()
{
Demo demo;
std::vector<std::thread> vec;
vec.push_back(std::thread([&demo](){
while(true)
{
for(int i=0; i<9; i++)
{
bool cur_flag = false;
if(demo.Get(i, cur_flag) == 0)
{
demo.Set(i, !cur_flag);
}
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
}
}));
vec.push_back(std::thread([&demo](){
while(true)
{
for(int i=0; i<9; i++)
{
bool cur_flag = false;
if(demo.Get(i, cur_flag)==0)
{
std::cout << "(" << i << "," << cur_flag <<")" << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
})
);
for(auto& thread:vec)
{
thread.join();
}
}
What more, the compiler does not complain about anything with -fsanitize=thread option.
Yes, this is safe.
Data races are best thought of as unsynchronized conflicting access (potential concurrent reads and writes).
std::thread construction imposes an order: the actions which preceded in code are guaranteed to come before the thread starts. So the map is completely populated before the concurrent accesses.
The library says standard types can only access the type itself, the function arguments, and the required properties of any container elements. std::map::find is non-const, but the standard requires that for the purposes of data races, it is treated as const. Operations on iterators are required to at most access (but not modify) the container. So the concurrent accesses to std::map are all non-modifying.
That leaves the load and store from the std::atomic<bool> which is race-free as well.
This should avoid data-race UB, since you aren't mutating the std::map data structure at all after starting either thread. And given the limited ways you're modifying the atomic values of the map, that's also safe.
You didn't provide accessor functions that would allow atomic RMW, so the only thing you can do is .store(flag) and .load(flag) with the default memory_order_seq_cst. Not .exchange or .compare_exchange_weak, or ^= 1 to flip.
Your if(Get) Set isn't equivalent to a flag ^= 1; atomic flip of that flag (although that doesn't compile as efficiently as one might like: Efficient way to toggle an atomic_bool).
If another thread was also flipping the same flag, they could step on each other. e.g. 10 total flips should get it back to the original value, but with separate atomic load and store, both threads could load the same value and then store the same value, resulting in only one flip for 2 (or many more) if(Get) Set operations across multiple threads.
Of course, if you don't have multiple threads writing the same flag, it is more efficient to separately load and store like you're doing.
Especially if you avoid the default memory_order_seq_cst, e.g. provide a std::memory_order ord = std::memory_order_seq_cst optional arg to your accessor functions, like std::atomic functions do. SC stores on most ISAs are more costly. (Especially on x86, where even mo_release is free in asm, but mo_seq_cst needs a full barrier.)
(But as discussed on Efficient way to toggle an atomic_bool std::atomic<bool> has no portable way to atomically flip it other than ^= 1, there isn't a member function that can take a memory_order arg. atomic<uint8_t> may be a better choice, taking the low bit as the actual boolean value.)
Since you need to be able to return failure, perhaps return a pointer to the std::atomic<bool> objects, with NULL indicating not found. That would let the caller use any std::atomic member function. But it does make misuse possible, holding onto the reference for too long.
I hear that const means thread-safe in C++11. Is that true?
Does that mean const is now the equivalent of Java's synchronized?
Are they running out of keywords?
I hear that const means thread-safe in C++11. Is that true?
It is somewhat true...
This is what the Standard Language has to say on thread-safety:
[1.10/4]
Two expression evaluations conflict if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location.
[1.10/21]
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
which is nothing else than the sufficient condition for a data race to occur:
There are two or more actions being performed at the same time on a given thing; and
At least one of them is a write.
The Standard Library builds on that, going a bit further:
[17.6.5.9/1]
This section specifies requirements that implementations shall meet to prevent data races (1.10). Every standard library function shall meet each requirement unless otherwise specified. Implementations may prevent data races in cases other than those specified below.
[17.6.5.9/3]
A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.
which in simple words says that it expects operations on const objects to be thread-safe. This means that the Standard Library won't introduce a data race as long as operations on const objects of your own types either
Consist entirely of reads --that is, there are no writes--; or
Internally synchronizes writes.
If this expectation does not hold for one of your types, then using it directly or indirectly together with any component of the Standard Library may result in a data race. In conclusion, const does mean thread-safe from the Standard Library point of view. It is important to note that this is merely a contract and it won't be enforced by the compiler, if you break it you get undefined behavior and you are on your own. Whether const is present or not will not affect code generation --at least not in respect to data races--.
Does that mean const is now the equivalent of Java's synchronized?
No. Not at all...
Consider the following overly simplified class representing a rectangle:
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
The member-function area is thread-safe; not because its const, but because it consist entirely of read operations. There are no writes involved, and at least one write involved is necessary for a data race to occur. That means that you can call area from as many threads as you want and you will get correct results all the time.
Note that this doesn't mean that rect is thread-safe. In fact, its easy to see how if a call to area were to happen at the same time that a call to set_size on a given rect, then area could end up computing its result based on an old width and a new height (or even on garbled values).
But that is alright, rect isn't const so its not even expected to be thread-safe after all. An object declared const rect, on the other hand, would be thread-safe since no writes are possible (and if you are considering const_cast-ing something originally declared const then you get undefined-behavior and that's it).
So what does it mean then?
Let's assume --for the sake of argument-- that multiplication operations are extremely costly and we better avoid them when possible. We could compute the area only if it is requested, and then cache it in case it is requested again in the future:
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[If this example seems too artificial, you could mentally replace int by a very large dynamically allocated integer which is inherently non thread-safe and for which multiplications are extremely costly.]
The member-function area is no longer thread-safe, it is doing writes now and is not internally synchronized. Is it a problem? The call to area may happen as part of a copy-constructor of another object, such constructor could have been called by some operation on a standard container, and at that point the standard library expects this operation to behave as a read in regard to data races. But we are doing writes!
As soon as we put a rect in a standard container --directly or indirectly-- we are entering a contract with the Standard Library. To keep doing writes in a const function while still honoring that contract, we need to internally synchronize those writes:
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
Note that we made the area function thread-safe, but the rect still isn't thread-safe. A call to area happening at the same time that a call to set_size may still end up computing the wrong value, since the assignments to width and height are not protected by the mutex.
If we really wanted a thread-safe rect, we would use a synchronization primitive to protect the non-thread-safe rect.
Are they running out of keywords?
Yes, they are. They have been running out of keywords since day one.
Source: You don't know const and mutable - Herb Sutter
This is an addition to K-ballo's answer.
The term thread-safe is abused in this context. The correct wording is: a const function implies thread-safe bitwise const or internally synchronised, as stated by Herb Sutter (29:43) himself
It should be thread-safe to call a const function from multiple threads simultaneously, without calling a non-const function at the same time in another thread.
So, a const function should not (and will not most of the time) be really thread-safe, as it may read memory (without internal synchronisation) that could be changed by another non-const function. In general, this is not thread-safe as a data race occurs even if only one thread is writing (and another reading the data).
See also my answer to the related question What is the definition of a thread safe function according to the C++11 (Language/Library) Standard?.
No! Counterexample:
#include <memory>
#include <thread>
class C
{
std::shared_ptr<int> refs = std::make_shared<int>();
public:
C() = default;
C(C const &other) : refs(other.refs)
{ ++*this->refs; }
};
int main()
{
C const c;
std::thread t1([&]() { C const dummy(c); });
std::thread t2([&]() { C const dummy(c); });
}
The copy-constructor of C is perfectly legitimate, but it is not thread-safe despite C being const.
Given the following:
class ReadWrite {
public:
int Read(size_t address);
void Write(size_t address, int val);
private:
std::map<size_t, int> db;
}
In read function when accessing an address which no previous write was made to I want to either throw exception designating such error or allow that and return 0, in other words I would like to either use std::map<size_t, int>::operator[]() or std::map<size_t, int>::at(), depending on some bool value which user can set. So I add the following:
class ReadWrite {
public:
int Read(size_t add) { if (allow) return db[add]; return db.at(add);}
void Write(size_t add, int val) { db[add] = val; }
void Allow() { allow = true; }
private:
bool allow = false;
std::map<size_t, int> db;
}
The problem with that is:
Usually, the program will have one call of allow or none at the beginning of the program and then afterwards many accesses. So, performance wise, this code is bad because it every-time performs the check if (allow) where usually it's either always true or always false.
So how would you solve such problem?
Edit:
While the described use case (one or none Allow() at first) of this class is very likely it's not definite and so I must allow user call Allow() dynamically.
Another Edit:
Solutions which use function pointer: What about the performance overhead incurred by using function pointer which is not able to make inline by the compiler? If we use std::function instead will that solve the issue?
Usually, the program will have one call of allow or none at the
beginning of the program and then afterwards many accesses. So,
performance wise, this code is bad because it every-time performs the
check if (allow) where usually it's either always true or always
false. So how would you solve such problem?
I won't, The CPU will.
the Branch Prediction will figure out that the answer is most likely to be same for some long time so it will able to optimize the branch in the hardware level very much. it will still incur some overhead, but very negligible.
If you really need to optimize your program, I think your better use std::unordered_map instead of std::map, or move to some faster map implementation, like google::dense_hash_map. the branch is insignificant compared to map-lookup.
If you want to decrease the time-cost, you have to increase the memory-cost. Accepting that, you can do this with a function pointer. Below is my answer:
class ReadWrite {
public:
void Write(size_t add, int val) { db[add] = val; }
// when allowed, make the function pointer point to read2
void Allow() { Read = &ReadWrite::read2;}
//function pointer that points to read1 by default
int (ReadWrite::*Read)(size_t) = &ReadWrite::read1;
private:
int read1(size_t add){return db.at(add);}
int read2(size_t add) {return db[add];}
std::map<size_t, int> db;
};
The function pointer can be called as the other member functions. As an example:
ReadWrite rwObject;
//some code here
//...
rwObject.Read(5); //use of function pointer
//
Note that non-static data member initialization is available with c++11, so the int (ReadWrite::*Read)(size_t) = &ReadWrite::read1; may not compile with older versions. In that case, you have to explicitly declare one constructor, where the initialization of the function pointer can be done.
You can use a pointer to function.
class ReadWrite {
public:
void Write(size_t add, int val) { db[add] = val; }
int Read(size_t add) { (this->*Rfunc)(add); }
void Allow() { Rfunc = &ReadWrite::Read2; }
private:
std::map<size_t, int> db;
int Read1(size_t add) { return db.at(add); }
int Read2(size_t add) { return db[add]; }
int (ReadWrite::*Rfunc)(size_t) = &ReadWrite::Read1;
}
If you want runtime dynamic behaviour you'll have to pay for it at runtime (at the point you want your logic to behave dynamically).
You want different behaviour at the point where you call Read depending on a runtime condition and you'll have to check that condition.
No matter whether your overhad is a function pointer call or a branch, you'll find a jump or call to different places in your program depending on allow at the point Read is called by the client code.
Note: Profile and fix real bottlenecks - not suspected ones. (You'll learn more if you profile by either having your suspicion confirmed or by finding out why your assumption about the performance was wrong.)
c++ pseudocode class:
Simple class which has a member variable, and mutex to control access to it.
I'm curious about the pro's and con's of managing the data and it's access.
In a multithreaded enviroment, is it wrong to use the approach to accessing and locking the member mutex in cbMethodA()?
I've seen samples where the members are accessed directly, and it seems incorrect to do that. The class exposes access via a public method for a reason.
Also, dereferencing a mutex to then lock it doesn't seem like best practice. Any comments?
Thanks
class A
{
public:
A():val(0);
~A();
int getVal(void);
static void cbMethodA();
static void cbMethodB();
private:
Mutex m_mutex;
int val;
}
int A::getVal(){
{
int returnVal = 0;
lockMutex(m_mutex);
returnVal = m_val;
unlock(mutex);
return returnVal;
}
void A::cbMethodA(void *ptr)
{
A* ptr = static_cast<A*> (ptr);
//get val
lockMutex(ptr->m_mutex);
//read val
int tempVal = ptr->m_val;
unlockMutex(ptr->m_mutex);
//do something with data
}
void A::cbMethodB(void *ptr)
{
A* ptr = static_cast<A*> (ptr);
//get val
int tempVal = ptr->getVal();
//process val....
}
This seems like a direct application of SPOT (Single Point Of Truth), a.k.a. DRY (Don't Repeat Yourself), two names for a single important idea. You've created a function for accessing val that performs some tasks that should always go along with accessing it. Unless there is some private, implementation-specific reason to access the member field directly, you should probably use the getter method you define. That way, if you change the synchronization mechanism that protects val, you only need to update one piece of code.
I can't think of any reason why "dereferencing a mutex to lock it" would be a bad thing, repeating yourself is a bad thing.
I hear that const means thread-safe in C++11. Is that true?
Does that mean const is now the equivalent of Java's synchronized?
Are they running out of keywords?
I hear that const means thread-safe in C++11. Is that true?
It is somewhat true...
This is what the Standard Language has to say on thread-safety:
[1.10/4]
Two expression evaluations conflict if one of them modifies a memory location (1.7) and the other one accesses or modifies the same memory location.
[1.10/21]
The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.
which is nothing else than the sufficient condition for a data race to occur:
There are two or more actions being performed at the same time on a given thing; and
At least one of them is a write.
The Standard Library builds on that, going a bit further:
[17.6.5.9/1]
This section specifies requirements that implementations shall meet to prevent data races (1.10). Every standard library function shall meet each requirement unless otherwise specified. Implementations may prevent data races in cases other than those specified below.
[17.6.5.9/3]
A C++ standard library function shall not directly or indirectly modify objects (1.10) accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function’s non-const arguments, including this.
which in simple words says that it expects operations on const objects to be thread-safe. This means that the Standard Library won't introduce a data race as long as operations on const objects of your own types either
Consist entirely of reads --that is, there are no writes--; or
Internally synchronizes writes.
If this expectation does not hold for one of your types, then using it directly or indirectly together with any component of the Standard Library may result in a data race. In conclusion, const does mean thread-safe from the Standard Library point of view. It is important to note that this is merely a contract and it won't be enforced by the compiler, if you break it you get undefined behavior and you are on your own. Whether const is present or not will not affect code generation --at least not in respect to data races--.
Does that mean const is now the equivalent of Java's synchronized?
No. Not at all...
Consider the following overly simplified class representing a rectangle:
class rect {
int width = 0, height = 0;
public:
/*...*/
void set_size( int new_width, int new_height ) {
width = new_width;
height = new_height;
}
int area() const {
return width * height;
}
};
The member-function area is thread-safe; not because its const, but because it consist entirely of read operations. There are no writes involved, and at least one write involved is necessary for a data race to occur. That means that you can call area from as many threads as you want and you will get correct results all the time.
Note that this doesn't mean that rect is thread-safe. In fact, its easy to see how if a call to area were to happen at the same time that a call to set_size on a given rect, then area could end up computing its result based on an old width and a new height (or even on garbled values).
But that is alright, rect isn't const so its not even expected to be thread-safe after all. An object declared const rect, on the other hand, would be thread-safe since no writes are possible (and if you are considering const_cast-ing something originally declared const then you get undefined-behavior and that's it).
So what does it mean then?
Let's assume --for the sake of argument-- that multiplication operations are extremely costly and we better avoid them when possible. We could compute the area only if it is requested, and then cache it in case it is requested again in the future:
class rect {
int width = 0, height = 0;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
cached_area_valid = ( width == new_width && height == new_height );
width = new_width;
height = new_height;
}
int area() const {
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
[If this example seems too artificial, you could mentally replace int by a very large dynamically allocated integer which is inherently non thread-safe and for which multiplications are extremely costly.]
The member-function area is no longer thread-safe, it is doing writes now and is not internally synchronized. Is it a problem? The call to area may happen as part of a copy-constructor of another object, such constructor could have been called by some operation on a standard container, and at that point the standard library expects this operation to behave as a read in regard to data races. But we are doing writes!
As soon as we put a rect in a standard container --directly or indirectly-- we are entering a contract with the Standard Library. To keep doing writes in a const function while still honoring that contract, we need to internally synchronize those writes:
class rect {
int width = 0, height = 0;
mutable std::mutex cache_mutex;
mutable int cached_area = 0;
mutable bool cached_area_valid = true;
public:
/*...*/
void set_size( int new_width, int new_height ) {
if( new_width != width || new_height != height )
{
std::lock_guard< std::mutex > guard( cache_mutex );
cached_area_valid = false;
}
width = new_width;
height = new_height;
}
int area() const {
std::lock_guard< std::mutex > guard( cache_mutex );
if( !cached_area_valid ) {
cached_area = width;
cached_area *= height;
cached_area_valid = true;
}
return cached_area;
}
};
Note that we made the area function thread-safe, but the rect still isn't thread-safe. A call to area happening at the same time that a call to set_size may still end up computing the wrong value, since the assignments to width and height are not protected by the mutex.
If we really wanted a thread-safe rect, we would use a synchronization primitive to protect the non-thread-safe rect.
Are they running out of keywords?
Yes, they are. They have been running out of keywords since day one.
Source: You don't know const and mutable - Herb Sutter
This is an addition to K-ballo's answer.
The term thread-safe is abused in this context. The correct wording is: a const function implies thread-safe bitwise const or internally synchronised, as stated by Herb Sutter (29:43) himself
It should be thread-safe to call a const function from multiple threads simultaneously, without calling a non-const function at the same time in another thread.
So, a const function should not (and will not most of the time) be really thread-safe, as it may read memory (without internal synchronisation) that could be changed by another non-const function. In general, this is not thread-safe as a data race occurs even if only one thread is writing (and another reading the data).
See also my answer to the related question What is the definition of a thread safe function according to the C++11 (Language/Library) Standard?.
No! Counterexample:
#include <memory>
#include <thread>
class C
{
std::shared_ptr<int> refs = std::make_shared<int>();
public:
C() = default;
C(C const &other) : refs(other.refs)
{ ++*this->refs; }
};
int main()
{
C const c;
std::thread t1([&]() { C const dummy(c); });
std::thread t2([&]() { C const dummy(c); });
}
The copy-constructor of C is perfectly legitimate, but it is not thread-safe despite C being const.