Reputation: 11072
TL;DR: What is meant by saying a specific function is 'thread-safe' as a data race occurs by simultaneously calling two possibly different functions? This question is especially relevant in the context of people telling "const means/implies thread-safe in C++11" [1][2]
Consider the following example:
class X {
int x, y; // are some more complex type (not supported by `std::atomic`)
std::mutex m;
public:
void set_x (int new_x) {x = new_x;} // no mutex
void get_x () const {return x;}
void set_y (int new_y) {
std::lock_guard<std::mutex> guard(m); // guard setter with mutex
y = new_y;
}
void get_y () const {return y;}
}
Is set_x
thread safe?
Off course, set_x
is not thread safe as calling it from two threads simultaneously results in a data race.
Are get_x
, get_y
and set_y
thread safe?
Two possible reasonings exists:
get_x
/get_y
/set_y
from two threads simultaneously does not result in a data race.get_x
(or get_y
) and set_x
(or set_y
) from two threads simultaneously results in a data race.Which one is the correct reasoning for each of those three functions?
Which reasoning is correct?
set_x
/get_x
, but fails for set_y
/get_y
, as this would result to the conclusion that set_y
and get_y
are thread safe, but class Y
isn't as calling set_y
and get_y
from two threads simultaneously results in a data race.Note that I have read the following related threads:
Upvotes: 0
Views: 1985
Reputation: 11072
Note that this my own opinionated answer based on my own research and provided input from others.
A function is thread safe iff it does not access (read or write) any memory that could be modified 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.
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 a 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.
Upvotes: -1
Reputation: 905
I'll also go with the your following statement:
A function is thread safe if it does not access any memory that could be modified without internal synchronization by another function. This seems to me the most consistent option, but is not the way it is often used.
As long as the shared variable is atomic and mutex are properly used to achieve synchronization, I don't see any problem with your above statement.
Upvotes: 0
Reputation: 473986
The C++ standard doesn't use terms like "thread-safe"; it uses more specific language. Humans use terms like "thread-safe" because we find them useful.
The common idea of a thread-safe function is a function that when called, assuming nobody else screws up, will not create a data race. get_x
is thread-safe; all things being equal, you may call it from any number of threads and get reasonable results. This is true even though it cannot be concurrently called with set_x
, as that causes a data race. But the cause of the data race is that you called a non-thread-safe function: set_x
.
The point of categorizing functions as "thread-safe" or not is to assign blame. If you call only "thread-safe" functions, then your code is "thread-safe". If you stray outside of the boundaries of "thread-safe" functions, then it is your straying outside of those boundaries that causes the data race.
It's not get_x
's fault that a concurrent set_x
call causes a data race.
As for the question of get/set_y
, as previously stated, "thread-safe" is not a computational term or a rigorously standard term. It is a human term, a simplification of the computational reality.
The rules of what is "thread-safe" is basically, "you can call any thread-safe function concurrently with any other thread-safe function". If you cannot call get_y
and set_y
concurrently, then they're not "thread-safe".
From a rigorous perspective, the accurate way to describe these two functions is that set_y
synchronizes with other calls to set_y
on the same object, and get_y
synchronizes with other calls to get_y
on the same object. The fact that we don't also say that they synchronize with each other tells you what you need to know.
From a simplified perspective, set_y
is "thread-safe"; get_y
is not. But you could also say that get_y
is "thread-safe" and set_y
is not. It doesn't really matter, since it's just a simplification.
And whether you declare get_y
const
is not what makes it "thread-safe". Sutter is saying that, if you write a const
function, it is your job to do it in a way such that it is "thread-safe". Therefore, get_y
is broken because it is not written in a thread-safe way, since it cannot be called thread-safely with other thread-safe functions.
Upvotes: 2