[英]Concurrent reads on non-atomic variable
我在尝试实现共享指针时遇到了这个问题。 让我们关注托管数据指针。 它的生命周期可以分为三个阶段:
我的问题是,鉴于这种情况,指针是否必须是原子的? 我认为这等效于:如果指针不是原子的,第二阶段会导致未定义的行为吗? 理想情况下,我希望听到从理论(语言律师)角度和实践角度进行讨论的答案。 例如,如果不是原子的,则阶段2在理论上可能是未定义的行为,但在实际平台上实际上是可以的。 为了实现共享指针,如果non-atomic可以,托管的指针可以是unique_ptr<T>
,否则必须是atomic<T*>
。
更新
我找到了标准文字(第1.10页p21):
如果程序的执行在不同线程中包含两个冲突的动作,则其中至少有一个不是原子的,并且在两个线程之前都没有发生,因此该程序的执行将引起数据竞争。 任何此类数据争用都会导致未定义的行为。
我猜并发读取不会归类为冲突操作。 可以肯定有人找到一些标准的文字吗?
规则是,如果一个以上的线程同时访问同一对象,并且其中至少有一个线程正在修改数据,则您将发生数据争用,并且程序的行为是不确定的。 如果没有人在修改对象,则并发访问不会有问题。
自己找到答案。 引用自第一段, C ++并发性中的 5.1.2节:
[...]如果两个线程都没有更新内存位置,就可以了; 只读数据不需要保护或同步。 如果任何一个线程正在修改数据,则存在争用条件的可能性,如第3章所述。
由于[intro.multithread]
中存在冲突的评估定义,因此并发读取任何变量(无论是否为原子)都不构成数据争用:
如果两个表达式评估之一修改一个内存位置,而另一个表达式访问或修改相同的内存位置,则这两个表达式评估会发生冲突 。
最近,此词已移至[intro.races]
,措辞非常微妙
如果两个表达式求值之一修改一个内存位置,而另一个表达式读取或修改了相同的内存位置,则这两个表达式求值会冲突 。
从访问到读取的更改发生在n4296和n4431草案之间。 多线程部分的拆分在n4582和n4604之间进行。
因此,我想您正在谈论持有计数器和指向所拥有数据的指针的结构:
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
的析构函数内,计数器通过具有获取释放内存顺序的读-修改-写操作递减:
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,这意味着在所有其他线程中,指向相同shared_counter
其他 shared_ptrs将不再访问此shared_counter
。 内存顺序确保了在其他线程中对该共享计数器执行的访问将在获取线程A中的值1之前发生(在其他线程中释放->在将调用析构函数的线程中获取)。
因此,无需将ptr
设为原子,因为计数器的递减会导致足够的排序。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.