Search code examples
c++c++11concurrencylanguage-lawyeratomic

Concurrent reads on non-atomic variable


I encounter this question while trying to implement a shared pointer. Let's focus on the managed data pointer. Its lifetime can be divided into three stages:

  1. Construction where there is no concurrent access.
  2. Concurrent reads on it (no writes).
  3. Destruction where there is no concurrent access. This is guaranteed by reference counting.

My question is, given this situation, is it necessary for the pointer to be atomic? I think it's equivalent to: will stage 2 lead to undefined behavior if the pointer is not atomic? Ideally, I want to hear an answer discussing from both a theoretical (language-lawyer) point of view and a practical point of view. For example, if not atomic, stage 2 may be undefined behavior theoretically, but is practically OK on actual platforms. For implementing shared pointer, if non-atomic is OK, the managed pointer can be unique_ptr<T>, otherwise it has to be atomic<T*>.

Update

I find the standard text (Section 1.10 p21):

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.

I guess concurrent reads do not classify as conflicting actions. Could somebody find some standard text about this to be sure?


Solution

  • Concurrent reads on any variable, whether atomic or not, do not constitute a data race, because of the definition of conflicting evaluations, found in [intro.multithread]:

    Two expression evaluations conflict if one of them modifies a memory location and the other one accesses or modifies the same memory location.

    Recently, this has moved to [intro.races] with a very subtle change in wording

    Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location.

    The change from accesses to reads took place between draft n4296 and n4431. The splitting of the multithreading section took place between n4582 and n4604.