Does const mean thread-safe in C++11?
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, includingthis
.
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'ssynchronized
?
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
Thread safety and `const`
The main problem with multiple threads is mutability. const restricts this, but since you can cast away the const-ness, it's not foolproof.
What is the definition of a thread safe function according to the C++11 (Language/Library) Standard?
Note that this my own opinionated answer based on my own research and provided input from others.
What is the definition of a thread-safe function?
A function is thread safe iff it does not access (read or write) any memory that could be modified by another function without internal synchronization: only set_y
is thread-safe.
Note that thread-safe is not explicitly defined by the C++ standard, which uses the term data races. See the answer of Nicol Bolas for more information: thread-safety is not always black and white.
A const function implies thread-safe bitwise const or internally synchronised
The term thread-safe is abused in the context of "a const function implies thread-safe".
What is meant by "a const function implies thread-safe", is that it should be safe to call the const function from multiple threads (without calling non-const function at the same time in another thread).
As Herb Sutter (29:43) stated himself, in this context, thread-safe means bitwise const or internally synchronised, which isn't really thread-safe if other non-const functions may be called at the same time.
Are static const variables thread-safe?
To be really safe you should do
static char const*const a[]
this inhibits modification of the data and all the pointers in the table to be modified.
BTW, I prefer to write the const
after the typename such that it is clear at a first glance to where the const
applies, namely to the left of it.
C++11 const - Is my code thread-safe?
No, it is not thread-safe since you (read) access _name
outside the mutex
, which breaks synchronization.
A possible solution is to use the std::call_once
mechanism offered by the standard library.
class Object
{
public:
Object() {}
const std::string& get_name() const
{
std::call_once(flag, [&] { _name = get_object_name(); });
return _name;
}
private:
std::string get_object_name() const; // <- Expensive function
mutable std::string _name;
mutable std::once_flag flag;
};
This guarantees that get_object_name()
is not called more than once. The first call will initialize the string
and concurrent calls will block until the lambda has finished.
Synchronization is fully taken care of, which means that any thread that gets the reference to the string
can safely read from it.
Is const function thread safe
The code you posted is not thread-safe. The rule is, if there is any chance that one thread might write to a variable while a second thread is concurrently accessing that same variable (i.e. either writing to it or reading it), then your program invokes undefined behavior (due to a race condition) and is not guaranteed to function correctly.
To avoid the race condition, you need to either synchronize the read-access inside GetSomeEnumVal()
with a mutex (the same way you did with the write-access inside SetSomeEnum()
) or alternatively change enum_val
to be an atomic type which is explicitly documented to be able to handle simultaneous unsynchronized access in a well-defined manner.
C++11 Singleton. Static variable is thread safe? Why?
In C++11 (and forward), the construction of the function local static AppSettings
is guaranteed to be thread-safe. Note: Visual Studio did not implement this aspect of C++11 until VS-2015.
The compiler will lay down a hidden flag along side of AppSettings
that indicates whether it is:
- Not constructed.
- Being constructed.
- Is constructed.
The first thread through will find the flag set to "not constructed" and attempt to construct the object. Upon successful construction the flag will be set to "is constructed". If another thread comes along and finds the flag set to "being constructed", it will wait until the flag is set to "is constructed".
If the construction fails with an exception, the flag will be set to "not constructed", and construction will be retried on the next pass through (either on the same thread or a different thread).
The object instance
will remain constructed for the remainder of your program, until main()
returns, at which time instance
will be destructed.
Every time any thread of execution passes through AppSettings::GetInstance()
, it will reference the exact same object.
In C++98/03, the construction was not guaranteed to be thread safe.
If the constructor of AppSettings
recursively enters AppSettings::GetInstance()
, the behavior is undefined.
If the compiler can see how to construct instance
"at compile time", it is allowed to.
If AppSettings
has a constexpr
constructor (the one used to construct instance
), and the instance
is qualified with constexpr
, the compiler is required to construct instance
at compile time. If instance
is constructed at compile time, the "not-constructed/constructed" flag will be optimized away.
About const and thread-safety of pure function objects
The easiest solution is to use a thread_local
buffer.
int T::myfunction(int arg) const
{
static thread_local U mybuffer;
// impl...
}
thread_local
will give you thread safety, as each thread will use its own buffer. Since it is static
inside a function, the buffer will be retained after each function call.
C++11 Thread-safe initialization of function-local static const objects
Since C++11 all static local variables are guaranteed to be initialized only once in a thread-safe manner.
As per cppreference:
If multiple threads attempt to initialize the same static local
variable concurrently, the initialization occurs exactly once (similar
behavior can be obtained for arbitrary functions withstd::call_once
).
Note: usual implementations of this feature use variants of the
double-checked locking pattern, which reduces runtime overhead for
already-initialized local statics to a single non-atomic boolean
comparison.
So, for your questions:
- yes
- yes
- yes
Related Topics
How to Make Cmake Output into a 'Bin' Dir
Normal Mapping Gone Horribly Wrong
Why Does C++ Allow Us to Surround the Variable Name in Parentheses When Declaring a Variable
When Are Static C++ Class Members Initialized
Why Does C++11'S Lambda Require "Mutable" Keyword For Capture-By-Value, by Default
How to Find Which Elements Are in the Bag, Using Knapsack Algorithm [And Not Only the Bag'S Value]
How to Write a Power Function Myself
Use 'Class' or 'Typename' For Template Parameters
"Downcasting" Unique_Ptr≪Base≫ to Unique_Ptr≪Derived≫
Difference Between Iostream and Iostream.H
Detecting Superfluous #Includes in C/C++
Why Does C++ Compilation Take So Long
Library Function For Permutation and Combination in C++
Why Are C++ Inline Functions in the Header
Why Does C++ Require a User-Provided Default Constructor to Default-Construct a Const Object