Chr*_*ula 1 c++ multithreading reference-counting shared-ptr
https://en.cppreference.com/w/cpp/memory/shared_ptr/use_count状态:
在多线程环境中,use_count 返回的值是近似值(典型实现使用 memory_order_relaxed 加载)
但这是否意味着use_count()在多线程环境中完全没有用呢?
考虑以下示例,其中该类Circular实现了一个循环缓冲区std::shared_ptr<int>。
向用户提供了一种方法 - ,它检查中元素get()的引用计数是否大于 1(我们不希望这样做,因为这意味着它由先前调用 的用户持有)。nextstd::array<std::shared_ptr<int>>get()
如果是<= 1,则将 的副本std::shared_ptr<int>返回给用户。
在这种情况下,用户是两个线程,除了喜欢调用get()循环缓冲区之外什么都不做——这就是他们的人生目的。
在实践中,当我执行该程序时,它会运行几个周期(通过将 a 添加counter到循环缓冲区类进行测试),然后抛出异常,抱怨下一个元素的引用计数器为> 1。
use_count()这是多线程环境下返回的值是近似值这一说法的结果吗?
是否有可能调整底层机制,使其具有确定性并按照我希望的方式运行?
如果我的想法是正确的 -在 的函数内部,元素use_count()的(或者更确切地说,用户的实际数量)next永远不应该增加到 1 以上,因为只有两个消费者,并且每次线程调用 时,它已经释放了旧的(已复制)(这又意味着剩余的驻留应该只有 1 的引用计数)。get()Circularget()std::shared_ptr<int>std::shared_ptr<int>Circular::ints_
#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();
}
Run Code Online (Sandbox Code Playgroud)
虽然use_count充满了问题,但现在的核心问题超出了这个逻辑。
假设线程t1采用shared_ptr索引 0,然后在完成其第一次循环迭代t2之前运行其循环两次t1。 t2将获取shared_ptr索引 1 处的数据,释放它,然后尝试获取shared_ptr索引 0 处的数据,并且会遇到失败条件,因为t1它只是落后了。
现在,也就是说,在更广泛的上下文中,它并不是特别安全,就好像用户创建了一个weak_ptr,完全有可能use_count在不通过此函数的情况下从 1 变为 2 。在这个简单的示例中,可以让它循环遍历索引数组,直到找到空闲的共享指针。