简体   繁体   English

为什么std :: atomic initialisation没有原子释放,所以其他线程可以看到初始值?

[英]Why doesn't std::atomic initialisation do atomic release so other threads can see the initialised value?

Something very odd turned up during the thread sanitising of proposed boost::concurrent_unordered_map and is recounted at this blog post . 提议的boost :: concurrent_unordered_map的线程清理过程中出现了一些非常奇怪的东西,并在这篇博客文章中进行了叙述 In short, bucket_type looks like this: 简而言之,bucket_type如下所示:

  struct bucket_type_impl
  {
    spinlock<unsigned char> lock;  // = 2 if you need to reload the bucket list
    atomic<unsigned> count; // count is used items in there
    std::vector<item_type, item_type_allocator> items;
    bucket_type_impl() : count(0), items(0) {  }
    ...

Yet the thread sanitiser claims that there is a race between the construction of a bucket_type and its first use, specifically when the count atomic is loaded from. 然而线程消毒者声称在bucket_type的构造和它的第一次使用之间存在竞争,特别是当加载计数原子时。 It turns out that if you initialise a std::atomic<> via its constructor, that initialisation is not atomic and therefore the memory location is not atomically released and therefore not visible to other threads, which is counterintuitive given it's an atomic, and that most atomic operations default to memory_order_seq_cst. 事实证明,如果你通过它的构造函数初始化std :: atomic <>, 那么初始化不是原子的 ,因此内存位置不是原子释放的,因此对其他线程不可见,这是违反直觉的,因为它是一个原子,并且大多数原子操作默认为memory_order_seq_cst。 You must therefore explicitly do a release store after construction to initialise the atomic with a value visible to other threads. 因此,您必须在构造之后显式执行发布存储,以使用其他线程可见的值初始化原子。

Is there some extremely pressing reason why std::atomic with a value consuming constructor does not initialise itself with release semantics? 是否有一些非常迫切的原因,为什么std :: atomic与值消耗构造函数不会使用发布语义初始化自己? If not, I think this is a library defect. 如果没有,我认为这是一个库缺陷。

Edit: Jonathan's answer is the better for the history as to why, but ecatmur's answer links to Alastair's defect report on the matter, and how it was closed by simply adding a note to say construction offers no visibility to other threads. 编辑: Jonathan的答案对于历史来说更好,但是ecatmur的回答链接到Alastair关于此事的缺陷报告,以及如何通过简单地添加注释来表示构造提供其他线程的可见性。 I'll therefore award the answer to ecatmur. 因此,我将奖励ecatmur。 Thanks to all who replied, I think the way is clear to ask for an extra constructor, it will at least stand out in the documentation that there is something unusual with the value consuming constructor. 感谢所有回复的人,我认为要求一个额外的构造函数的方式很明显,它至少会在文档中脱颖而出,值得使用构造函数。

Edit 2: I ended up raising this as a defect in the C++ language with the committee, and Hans Boehm who chairs the Concurrency part feels this is not an issue for the following reasons: 编辑2:我最终将此作为C ++语言中的缺陷与委员会一起提出,并且并发部分主持人Hans Boehm认为这不是问题,原因如下:

  1. No present C++ compiler in 2014 treats consume as different to acquire. 2014年没有现有的C ++编译器将消费视为与获取不同。 As you will never, in real world code, pass an atomic to another thread without going through some release/acquire, the initialisation of the atomic would be made visible to all threads using the atomic. 正如您将永远不会,在现实世界的代码中,将原子传递给另一个线程而不经过一些释放/获取,原子的初始化将使用原子对所有线程可见。 I think this fine until compilers catch up, and before that the Thread Sanitiser will warn on this. 我觉得这很好,直到编译器赶上来,在此之前,Thread Sanitiser会对此发出警告。

  2. If you're doing mismatched consume-acquire-release like I am (I am using a release-inside-lock/consume-outside-lock atomic to speculatively avoid a release-acquire spinlock where it was unnecessary) then you're a big enough boy to know you must manually store release atomics after construction. 如果你像我一样做了不匹配的消费 - 获取 - 释放(我正在使用一个release-inside-lock / consume-outside-lock原子来推测性地避免发布 - 获取自旋锁,这是不必要的)那么你就是一个大的足够的男孩知道你必须在施工后手动储存释放原子。 That is probably a fair point. 这可能是一个公平的观点。

It's because the converting constructor is constexpr , and constexpr functions can't have side effects such as atomic semantics. 这是因为转换构造函数是constexprconstexpr函数不能具有原子语义等副作用。

In DR846 , Alastair Meredith writes: DR846中 ,Alastair Meredith写道:

I'm not sure if the initialization is implied by use of constexpr keyword (which restricts the form of a constructor) but even if that is the case, I think it is worth spelling out explicitly as the inference would be far too subtle in that case. 我不确定是否使用constexpr关键字(它限制了构造函数的形式)暗示了初始化,但即使是这种情况,我认为值得明确拼写,因为推断会过于微妙。案件。

The resolution for that defect (by Lawrence Crowl) was to document the constructor with the note: 该缺陷的解决方案(劳伦斯·克劳尔)是用构造函数记录的:

[ Note: Construction is not atomic. [ 注意:构造不是原子的。 —end note ] - 尾注 ]

The note was then expanded to the current wording, giving an example of a possible memory race (via memory_order_relaxed operations communicating the address of the atomic) in DR1478 . 然后将该注释扩展为当前的措辞,给出了memory_order_relaxed可能的内存竞争(通过memory_order_relaxed原子地址的memory_order_relaxed操作)的示例

The reason that the converting constructor needs to be constexpr is (primarily) to allow static initialization. 转换构造函数需要constexpr原因(主要)是允许静态初始化。 In DR768 we see: DR768中我们看到:

Further discussion: why is the ctor labeled "constexpr"? 进一步的讨论:为什么ctor被标记为“constexpr”? Lawrence [Crowl] said this permits the object to be statically initialized, and that's important because otherwise there would be a race condition on initialization. Lawrence [Crowl]说这允许对象静态初始化,这很重要,否则初始化会出现竞争条件。

So: making the constructor constexpr eliminates race conditions on static-lifetime objects, at the cost of a race in dynamic-lifetime objects that only occurs in fairly contrived situations, since for a race to occur the memory location of the dynamic-lifetime atomic object must be communicated to another thread in a way that does not result in the value of the atomic object being also synchronized to that thread. 因此:使构造函数constexpr消除静态生命周期对象上的竞争条件,代价是动态生命周期对象中的竞争只发生在相当人为的情况下,因为竞争发生在动态生命周期原子对象的内存位置必须以不会导致原子对象的也同步到该线程的方式传递给另一个线程。

That is an intentional design choice (there's even a note in the standard warning about it) and I think it was done in an attempt to be compatible with C. 这是一个有意的设计选择(在关于它的标准警告中甚至还有一个注释),我认为它是为了与C兼容而完成的。

The C++11 atomics were designed so that they could be used by WG14 for C as well, using the non-member functions such as atomic_load with types such as atomic_int rather than the member functions of the C++-only std::atomic<int> . C ++ 11原子的设计使它们也可以被WG14用于C,使用非成员函数,例如atomic_load ,类型如atomic_int而不是C ++的成员函数 - 只有std::atomic<int> In the original design, the atomic_int type has no special properties and atomicity is only achieved through atomic_load() and other functions. 在原始设计中, atomic_int类型没有特殊属性,只有atomic_load()和其他函数才能实现原子性。 In that model atomic_init is not an atomic operation, it just initializes a POD. 在该模型中, atomic_init不是原子操作,它只是初始化POD。 Only a subsequent atomic_store(&i, 1) call would be atomic. 只有后续的atomic_store(&i, 1)调用atomic_store(&i, 1)原子的。

In the end, WG14 decided to do things differently, adding the _Atomic specifier which makes the atomic_int type have magic properties. 最终,WG14决定做不同的事情,加上_Atomic符这使得atomic_int类型具有魔法属性。 I'm not sure whether that means initialization of C atomics could be atomic (as it stands, atomic_init in C11 and C++11 is documented to be non-atomic), so maybe the C++11 rule is unnecessary. 我不确定这是否意味着C atomics的初始化可能是原子的(因为它,C11中的atomic_init和C ++ 11被记录为非原子的),所以C ++ 11规则可能是不必要的。 I suspect people will argue that there are good performance reason to keep initialization non-atomic, as interjay's comment above says, you need to send some notification to the other thread that the obejct is constructed and ready to be read from, so that notification could introduce the necessary fencing. 我怀疑人们会认为有很好的性能原因可以保持初始化非原子性,正如上面的interjay的评论所说,你需要向另一个线程发送一些通知,告知构造了obejct并准备好从中读取,以便通知可以介绍必要的围栏。 Doing it once for the std::atomic initialization and then a second time to say the object is constructed could be wasteful. std::atomic初始化做一次,然后第二次说构造对象可能是浪费。

I'd say, it's because a construction is never a thread communication operation: When you construct an object, you fill formerly uninitialized memory with sensible values. 我会说,这是因为构造永远不是一个线程通信操作:当你构造一个对象时,你用以明智的值填充以前未初始化的内存。 There is no way for another thread to tell whether that operation has finished unless it is explicitely communicated by the constructing thread . 除非构造线程明确地传达,否则另一个线程无法判断该操作是否已完成。 If you race with construction anyway, you immediately have undefined behavior. 如果你无论如何都在进行建筑竞赛,你会立即出现未定义的行为。

Since the creating thread must explicitly publish its success on constructing a value before another thread can be allowed to use it, there is simply no point in synchronizing constructors. 由于创建线程必须在允许另一个线程使用它之前显式地发布其构造值的成功,因此在同步构造函数中没有任何意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么不能 std::atomic<t> 被交换?</t> - Why can't std::atomic<T> be swapped? 使用std :: atomic :: compare_exchange_strong时,写入std :: atomic是否可以被其他线程看不到? - Can a write to std::atomic go unseen by other threads while using std::atomic::compare_exchange_strong? 我如何在 std::atomic 上实现一个简单的自旋锁<T>这样编译器就不会优化它? - How do I implement a simple spinlock on std::atomic<T> so that the compiler doesn't optimize it out? Eclipse为什么认为clang不支持std :: atomic - Why does Eclipse think clang doesn't support std::atomic 为什么TBB无法将`int`转换为`const tbb :: atomic <unsigned int> &`,但std :: atomic可以吗? - Why can't TBB cast `int` to `const tbb::atomic<unsigned int>&`, but std::atomic can? 原子释放可以被“覆盖”吗? - Can an atomic release be “overwritten”? sig_atomic_t和std :: atomic &lt;&gt;是否可以互换? - Are sig_atomic_t and std::atomic<> interchangable? 如果跨 2 个线程使用,是否值得将 size_t 声明为 std::atomic? - Is is worth to declare a size_t as std::atomic if used across 2 threads? 原子的<T> .load() 与 std::memory_order_release - atomic<T>.load() with std::memory_order_release 可以使用std :: atomic内存屏障在线程之间传输非原子数据吗? - Can std::atomic memory barriers be used to transfer non-atomic data between threads?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM