shared_ptr :: unique()指示只有一个线程吗?
我有一个工作线程,为我的渲染线程提供了std :: shared_ptr< bitmap>
作为我从GPU下载纹理数据的一部分。这两个线程都取决于std :: shared_ptr< ...> :: simel()
以确定其他线程是否使用位图完成。该共享指针的其他副本都没有发挥作用。
这是行为的修剪图表:
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
是否可以安全地使用unique()
或use_count()== 1
来实现这一目标?
edit :
啊...因为unique()
在这种情况下是不安全的,我认为我将尝试使用std :: shared_ptr&std之类的东西:: pap&lt; bitmap,std :: atomic&lt; bool&gt;&gt;&gt;&gt; bitmap;
相反,在填充原子时将其翻转。
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. Both threads depend on std::shared_ptr<...>::unique()
to determine if the other thread is done with the Bitmap. 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?
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;
instead, flipping the atomic when it's filled.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不,这不是安全的。调用
unique
(或use_count
)的调用并不意味着任何同步。如果工人线程观察
bitmap.unique()
为true
,这并不意味着在fillbitMap(*it); /code>和由
bitmap-&gt; savetodisk();
制造的。该功能调用可以作为放松的负载实现,该负载没有所需的获取释放效果。没有任何内存排序,您可能会有数据竞赛和不确定的行为。
这就是为什么
std :: shared_ptr :: unique
用C ++ 17弃用并使用C ++ 20删除,因为它很容易以这种方式滥用。只需使用use_count()== 1
也不起作用。它有完全相同的问题。我猜它也没有删除,因为即使在多线程环境中,也可能会有有效的情况。对于单线程上下文(没有任何问题),仍然应该有一种方法来实现unique
。根据删除
std :: shared_ptr :: unique
的提议,许多实现实际上使用了轻松的负载,这意味着问题实际上可能在实际实施中发生,请参见如果您在X86这样的体系结构上,您可能会因在硬件级别上自动的负载而保存。
但是,C ++内存模型在此处没有任何订购保证,我认为(例如,这里可能是错误的)是ARM的一个示例,即正常(放松)载荷不会自动自动获得/发布语义。
无论如何,由于没有内存订购保证,我认为编译器仍然可以执行任何情况下会引起问题的转换。例如,由于
unqiue
并不意味着任何同步,因此编译器可以在实际加载参考计数器之前加载bitmap-&gt; savetodisk();
中使用的数据。如果参考计数器上的条件未得到满足,则可能是不必要的,但是我看不到任何阻止编译器添加多余负载的任何东西。在这些加载
fillbitMap(*it);
的加载之间,并且可能发生渲染线程的破坏std :: shardy_ptr
可能发生存储在fillBitMap(*it);
中。No, it is not safe. A call to
unique
(or touse_count
) does not imply any synchronization.If e.g. the worker thread observes
bitmap.unique()
astrue
, this does not imply any memory ordering on the accesses made byfillBitmap(*it);
and those made bybitmap->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. Just usinguse_count() == 1
doesn't work either. 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 foruse_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 implementingunique
for a single-threaded context (where it doesn't have any problems).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.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.
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.
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 inbitmap->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);
and the destruction of the rendering threadsstd::shared_ptr
could happen, causing the worker thread to use data from before the stores infillBitmap(*it);
.