指出:
在多线程环境中,用use_count返回的值近似(典型的实现使用memory_order_raxed load)
但这是否意味着 use_count()
在多线程环境中完全没有用?
考虑以下示例,其中循环
类实现 std :: shared_ptr< int>
的圆形缓冲区。
将一种方法提供给用户 - get()
,该方法检查
element的参考计数是否 std :: array&lt< std :: shardy_ptr< int>>
大于1(我们不想要,因为这意味着它是由以前称为 get()
)的用户持有的。
如果是< = 1
,则返回给用户的 std :: shared_ptr> int>
的副本。
在这种情况下,用户是两个线程,除了爱上 get()
上,圆形缓冲区上什么都不做,这是他们生活中的目的。
当我执行程序时,在实践中会发生什么是用于几个周期的运行(通过在圆形缓冲区类中添加 Counter
测试),之后它引发了例外,抱怨该参考计数器的参考计数器下一个元素是> 1
。
这是否是 use_count()
返回的值在多线程环境中近似的结果?
是否有可能调整基本机制,使其像我希望的那样做到这一点,并表现得很行为?
如果我的想法是正确的 - use_count()
(或者是
的实际用户数量),当 element 元素在 get get get( )
循环
的函数,因为只有两个消费者,并且每次线程调用 get()
时,它已经发布了其旧(复制) std :: shared_ptr< int>
(这又意味着剩下的 std :: shared_ptr&lt> 居住在 coundular :: ints _ ints _
应该具有一个参考数量仅为1)。
#include <mutex>
#include <array>
#include <memory>
#include <exception>
#include <thread>
class Circular {
public:
Circular() {
for (auto& i : ints_) { i = std::make_shared<int>(0); }
}
std::shared_ptr<int> get() {
std::lock_guard<std::mutex> lock_guard(guard_);
index_ = index_ % 2; // Re-set the index pointer.
if (ints_.at(index_).use_count() > 1) {
// This shouldn't happen - right? (but it does)
std::string excp = std::string("OOPSIE: ") + std::to_string(index_) + " " + std::to_string(ints_.at(index_).use_count());
throw std::logic_error(excp);
}
return ints_.at(index_++);
}
private:
std::mutex guard_;
unsigned int index_{0};
std::array<std::shared_ptr<int>, 2> ints_;
};
Circular circ;
void func() {
do {
auto scoped_shared_int_pointer{circ.get()};
}while(1);
}
int main() {
std::thread t1(func), t2(func);
t1.join(); t2.join();
}
https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count states:
In multithreaded environment, the value returned by use_count is approximate (typical implementations use a memory_order_relaxed load)
But does this mean that use_count()
is totally useless in a multi-threaded environment?
Consider the following example, where the Circular
class implements a circular buffer of std::shared_ptr<int>
.
One method is supplied to users - get()
, which checks whether the reference count of the next
element in the std::array<std::shared_ptr<int>>
is greater than 1 (which we don't want, since it means that it's being held by a user which previously called get()
).
If it's <= 1
, a copy of the std::shared_ptr<int>
is returned to the user.
In this case, the users are two threads which do nothing at all except love to call get()
on the circular buffer - that's their purpose in life.
What happens in practice when I execute the program is that it runs for a few cycles (tested by adding a counter
to the circular buffer class), after which it throws the exception, complaining that the reference counter for the next element is > 1
.
Is this a result of the statement that the value returned by use_count()
is approximate in a multi-threaded environment?
Is it possible to adjust the underlying mechanism to make it, uh, deterministic and behave as I would have liked it to behave?
If my thinking is correct - use_count()
(or rather the real number of users) of the next
element should never EVER increase above 1 when inside the get()
function of Circular
, since there are only two consumers, and every time a thread calls get()
, it's already released its old (copied) std::shared_ptr<int>
(which in turn means that the remaining std::shared_ptr<int>
residing in Circular::ints_
should have a reference count of only 1).
#include <mutex>
#include <array>
#include <memory>
#include <exception>
#include <thread>
class Circular {
public:
Circular() {
for (auto& i : ints_) { i = std::make_shared<int>(0); }
}
std::shared_ptr<int> get() {
std::lock_guard<std::mutex> lock_guard(guard_);
index_ = index_ % 2; // Re-set the index pointer.
if (ints_.at(index_).use_count() > 1) {
// This shouldn't happen - right? (but it does)
std::string excp = std::string("OOPSIE: ") + std::to_string(index_) + " " + std::to_string(ints_.at(index_).use_count());
throw std::logic_error(excp);
}
return ints_.at(index_++);
}
private:
std::mutex guard_;
unsigned int index_{0};
std::array<std::shared_ptr<int>, 2> ints_;
};
Circular circ;
void func() {
do {
auto scoped_shared_int_pointer{circ.get()};
}while(1);
}
int main() {
std::thread t1(func), t2(func);
t1.join(); t2.join();
}
发布评论
评论(2)
尽管
use_count
充满了问题,但目前的核心问题不在该逻辑之外。假设线程
t1
在索引0处采用sharone_ptr
,然后t2
在t1
完成其第一个之前,将其循环运行两次循环迭代。t2
将在索引1处获得shared_ptr
,然后释放它,然后尝试在索引0中获取sharone_ptr
,因为t1
只是在后面运行。现在,在更广泛的情况下,它并不是特别安全,好像用户创建
feek_ptr
,use_count
完全有可能在没有1到2的情况下使用通过此功能。在这个简单的示例中,将其循环循环遍历索引数组,直到找到免费的共享指针为止。While
use_count
is fraught with problems, the core issue right now is outside of that logic.Assume thread
t1
takes theshared_ptr
at index 0, and thent2
runs its loop twice beforet1
finishes its first loop iteration.t2
will obtain theshared_ptr
at index 1, release it, and then attempt to acquire theshared_ptr
at index 0, and will hit your failure condition, sincet1
is just running behind.Now, that said, in a broader context, it's not particularly safe, as if a user creates a
weak_ptr
, it's entirely possible for theuse_count
to go from 1 to 2 without passing through this function. In this simple example, it would work to have it loop through the index array until it finds the free shared pointer.use_count
仅用于调试,不应使用。如果您想知道何时没有其他人对指针的参考,只需让共享的指针死去并使用自定义eleter来检测它,并对现在未使用的指针做任何您需要做的事情。这是您如何在代码中实现此信息的一个示例:
保留未使用索引的列表,当共享指针被销毁时,自定义deleter将索引返回回到准备在下一个呼叫中使用的未使用索引列表,
获取
。use_count
is for debugging only and shouldn't be used. If you want to know when nobody else has a reference to a pointer any more just let the shared pointer die and use a custom deleter to detect that and do whatever you need to do with the now unused pointer.This is an example of how you might implement this in your code:
A list of unused indexes is kept, when the shared pointer is destroyed the custom deleter returns the index back to the list of unused indexes ready to be used in the next call to
get
.