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?
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:
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
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
std::string
implementations, thread-safety of which came up in a recent question, because std::string::string(const std:string& other)
is not permitted to mutate a use count in other
. Do you agree? What implications are there for std::shared_ptr
? –
Forwarder std::string
is worded in a way that already forbids COW. I don't remember the specifics, though... –
Consumption r.width() * r.height()
) would never be thread-safe even if the object was thread-safe internally. –
Blarney const
function alters a mutable
variable then it does modify the value stored at that memory location. Simply, this modification does not turn out to create data races per [1.10/21] because it is atomic. –
Jessi const
function you can modify a mutable
variable, as long as you do that atomically (i.e. with proper synchronization)". However, [17.6.5.9/3] says: [an STL function] "shall not directly or indirectly modify" [its arguments by (among other things) calling one of their const
member functions]. But since those const
member functions can modify mutable
variables, this means modifying a mutable
variable is not seen a "modify" per [17.6.5.9/3] as long as you do it atomically. And I cannot find where this is stated. –
Jessi std::lock_guard
needed inside the set_size
to make the area
function thread-safe (i.e. comply with the requirements of the Standard Library)? –
Rianna area
is thread safe even if it access memory that could be changed by set_size
simultaneously resulting a data race? See this related question. –
Rianna 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?.
const
function can read memory that could be changed by another const
function, too. (Imagine a const
function that does ++this->p->value
.) –
Petronel ++this->p->value
(without internal synchronisation), it is not bitwise const
. So, it violates the rule anyway. In my last paragraph I try to explain that it is ok to make a const function not really thread safe (as long as it bitwise const or internally synchronised), i.e. it is allowed to read memory without internal synchronisation. So, calling only const functions is (or should be) thread-safe, but it is ok that your implementation is not thread safe if a const and a non-const function are called simultaneously. The caller should avoid this from happening. –
Rianna ++this->p->value
, it is not bitwise const. So, it should be internally synchronised, i.e. each access (read and write) to this->p->value
should be internally synchronised (mutexes, atomic operations). In this case the const function will be "really" thread-safe (independent of any other function that is called simultaneously and concerning accessing this->p->value
). –
Rianna #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); });
}
C
is perfectly legitimate, but it is not thread-safe despite C
being const
.K-Ballo
's answer. This statement is not about a hard requirement that is enforced by the compiler (or C++ Standard Language), but an assumption from the Standard library. So, all functions interacting with the standard library should obey this rule to ensure that no data races occur. Assuming interaction with the standard library is common, all code should obey this rule as good practice. –
Rianna const
is thread-safe. –
Petronel © 2022 - 2024 — McMap. All rights reserved.
const
means thread-safe. That would be nonsense, since otherwise it'd mean you should be able to just go ahead and mark every thread-safe method asconst
. Rather, the question we're really asking isconst
IMPLIES thread-safe, and that's what this discussion is about. – Petronel