m7913d
m7913d

Reputation: 11072

What is the definition of a thread safe function according to the C++11 (Language/Library) Standard?

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:

  1. Yes, they are thread safe, as calling get_x/get_y/set_y from two threads simultaneously does not result in a data race.
  2. No, they are not thread safe, as calling 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?


Question summary

Which reasoning is correct?

  1. A function is thread safe iff calling it from two threads simultaneously does not result in a data race. Could work for 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.
  2. A function is thread safe iff 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 (see related threads).

Related threads

Note that I have read the following related threads:

Upvotes: 0

Views: 1985

Answers (3)

m7913d
m7913d

Reputation: 11072

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 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 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

Manifest Man
Manifest Man

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

Nicol Bolas
Nicol Bolas

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

Related Questions