简体   繁体   English

shared_ptr::unique() 是否表明只有一个线程拥有它?

[英]Is shared_ptr::unique() indicative that only one thread owns it?

I have a worker thread giving my rendering thread a std::shared_ptr<Bitmap> as part of how I download texture data from the GPU.我有一个工作线程给我的渲染线程一个std::shared_ptr<Bitmap>作为我如何从 GPU 下载纹理数据的一部分。 Both threads depend on std::shared_ptr<...>::unique() to determine if the other thread is done with the Bitmap.两个线程都依赖于std::shared_ptr<...>::unique()来确定另一个线程是否完成了位图。 No other copies of this shared pointer are in play.此共享指针没有其他副本在起作用。

Here's a trimmed down chart of the behavior:这是行为的精简图表:

          worker thread             |            rendering thread
----------------------------------- + -------------------------------------------------
std::shared_ptr<Bitmap> bitmap;     |  std::queue<std::shared_ptr<Bitmap>> copy;
{                                   |  {
    std::scoped_lock lock(mutex);   |     std::scoped_lock lock(mutex);
    queue.push_back(bitmap);        |     std::swap(queue, copy);
}                                   |  }
...                                 |  for(auto it = copy.begin(); it != copy.end(); ++it) 
                                    |     if (it->unique() == false)
if (bitmap.unique())                |         fillBitmap(*it); // writing to the bitmap
    bitmap->saveToDisk(); // reading       

Is it safe to use unique() or use_count() == 1 to accomplish this?使用unique()use_count() == 1来完成此操作是否安全?

edit :编辑

Ah... since unique() is unsafe in this case, I think instead I'm going to try something like std::shared_ptr<std::pair<Bitmap, std::atomic<bool>>> bitmap;啊...因为在这种情况下unique()是不安全的,所以我想我会尝试类似std::shared_ptr<std::pair<Bitmap, std::atomic<bool>>> bitmap; instead, flipping the atomic when it's filled.相反,当它被填充时翻转原子。

No, it is not safe.不,这不安全。 A call to unique (or to use_count ) does not imply any synchronization.unique (或use_count )的调用并不意味着任何同步。

If eg the worker thread observes bitmap.unique() as true , this does not imply any memory ordering on the accesses made by fillBitmap(*it);例如,如果工作线程将bitmap.unique()观察为true ,这并不意味着对fillBitmap(*it); and those made by bitmap->saveToDisk();以及那些由bitmap->saveToDisk(); . . The function call may be implemented as a relaxed load, which doesn't have the required acquire-release effect.函数调用可以实现为宽松的负载,它没有所需的获取-释放效果。

Without any memory ordering, you will likely have a data race and undefined behavior as consequence.如果没有任何内存排序,您可能会遇到数据竞争和未定义的行为。

This is why std::shared_ptr::unique was deprecated with C++17 and removed with C++20, since it is so easy to misuse in this way.这就是为什么std::shared_ptr::unique在 C++17 中被弃用并在 C++20 中被移除的原因,因为它很容易以这种方式被滥用。 Just using use_count() == 1 doesn't work either.仅使用use_count() == 1也不起作用。 It has the exact same issues.它有完全相同的问题。 I guess it wasn't removed as well, because there may be valid uses cases even in multi-threaded environments for use_count if you only need it to get a fuzzy idea of the number of current users and because there should still be a way of implementing unique for a single-threaded context (where it doesn't have any problems).我想它也没有被删除,因为即使在多线程环境中也可能有有效的用例use_count如果你只需要它来模糊地了解当前用户的数量并且因为仍然应该有一种方法为单线程上下文实现unique (没有任何问题)。

According to the proposal to remove std::shared_ptr::unique , many implementations do in fact use a relaxed load, implying that problems may actually happen on real implementations, see https://open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0521r0.html .根据删除std::shared_ptr::unique的提议,许多实现实际上确实使用了宽松的负载,这意味着问题实际上可能发生在实际实现中,请参阅https://open-std.org/jtc1/sc22/wg21 /docs/papers/2016/p0521r0.html


If you are on an architecture like x86 you may be saved by the fact that on the hardware level a relaxed load is automatically an acquire load.如果您在 x86 之类的体系结构上,您可能会因为在硬件级别上的轻松负载自动成为获取负载这一事实而节省您的时间。

However, the C++ memory model does not make any ordering guarantees here and I think (might be wrong here) for example ARM is an example for an architecture where normal (relaxed) loads don't automatically have acquire/release semantics.但是,C++ 内存模型在这里没有做出任何排序保证,我认为(在这里可能是错误的)例如 ARM 是正常(宽松)负载不会自动具有获取/释放语义的架构的示例。

And in any case, because there are no memory ordering guarantees, I think the compiler could still perform transformations that will cause issues in any case.无论如何,由于没有内存排序保证,我认为编译器仍然可以执行在任何情况下都会导致问题的转换。 For example, because unqiue doesn't imply any synchronization, the compiler could load data used in bitmap->saveToDisk();例如,因为unqiue不暗示任何同步,编译器可以加载bitmap->saveToDisk(); before actually loading the reference counter.在实际加载引用计数器之前。 The load may be unnecessary if the condition on the reference counter turns out to not be fulfilled, but I don't see anything preventing the compiler from adding a superfluous load.如果引用计数器上的条件被证明没有得到满足,则加载可能是不必要的,但我没有看到任何阻止编译器添加多余负载的东西。

Inbetween these loads some stores from fillBitmap(*it);在这些之间从fillBitmap(*it); and the destruction of the rendering threads std::shared_ptr could happen, causing the worker thread to use data from before the stores in fillBitmap(*it);并且可能发生渲染线程std::shared_ptr的破坏,导致工作线程使用存储在fillBitmap(*it); . .

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM