并发读取非原子变量

Concurrent reads on non-atomic variable

我在尝试实现共享指针时遇到了这个问题。让我们关注托管数据指针。它的生命周期可分为三个阶段:

  1. 没有并发访问的构造。
  2. 并发读取(无写入)。
  3. 没有并发访问的破坏。这是由引用计数保证的。

我的问题是,鉴于这种情况,指针是否有必要是原子的?我认为这等同于:如果指针不是原子的,阶段 2 会导致未定义的行为吗?理想情况下,我想听到从理论(语言律师)的角度和实践的角度讨论的答案。例如,如果不是原子的,理论上阶段 2 可能是未定义的行为,但在实际平台上实际上是可以的。为了实现共享指针,如果非原子是可以的,托管指针可以是unique_ptr<T>,否则必须是atomic<T*>.

更新

我找到标准文本(第 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.

我想并发读取不会被归类为冲突操作。有人可以找到一些关于此的标准文本吗?

规则是,如果多个线程同时访问同一个对象,并且这些线程中至少有一个正在修改数据,那么就会发生数据竞争,程序的行为是不确定的。如果没有人修改对象,则并发访问没有问题。

所以我想你是在谈论保存计数器和指向拥有数据的指针的结构:

template<class ValueType>
struct shared_counter{
  std::atomic<int> count=0;
  const std::unique_ptr<ValueType> ptr;
  //Your question is: Does ptr should be atomic?
  //... This is a dumb implementation, only focusing on the subject.
  };

实际上,ptr 不需要是原子的,因为如果适当地实现引用计数,所有对 ptr 的访问都将在 shared_counter.

的析构之前排序

为了确保在 shared_ptr 的析构函数中,计数器通过具有 acquire-release 内存顺序的 read-modify-write 递减:

template<class ValueType>
struct shared_ptr{
   shared_counter<ValueType>* counted_ptr;
   //...
   void reset(){
     if (counted_ptr->count.fetch_sub(1,std::memory_order_acq_rel) == 1)
       counter_ptr->~shared_counter<ValueType>();
     counter_ptr=nullptr;
     }
   };

多亏了这个内存顺序,如果在线程 A 中获取的 count 的值为 1,这意味着在所有其他线程中 other shared_ptrs 指向same shared_counter 将不再访问此 shared_counter。内存顺序确保在这些其他线程中对此 shared_counter 执行的访问将发生在线程 A 中获取值 1 之前。(在其他线程中释放 -> 在将调用析构函数的线程中获取)。

所以不需要 ptr 是原子的,因为计数器递减会导致足够的排序。

自己找答案。引自 C++ Concurrency in Action 中第 5.1.2 节的第一段:

[...] If neither thread is updating the memory location, you’re fine; read-only data doesn’t need protection or synchronization. If either thread is modifying the data, there’s a potential for a race condition, as described in chapter 3.

并发读取任何变量,无论是否是原子的,都不会构成数据竞争,因为在 [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.

最近,这已经转移到 [intro.races],措辞上有非常细微的变化

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

accessesreads 的变化发生在草案 n4296 和 n4431 之间。多线程部分的拆分发生在n4582和n4604之间