std::shared_ptr<T>.use_counter() 的问题

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)

Dav*_*e S 5

虽然use_count充满了问题,但现在的核心问题超出了这个逻辑。

假设线程t1采用shared_ptr索引 0,然后在完成其第一次循环迭代t2之前运行其循环两次t1t2将获取shared_ptr索引 1 处的数据,释放它,然后尝试获取shared_ptr索引 0 处的数据,并且会遇到失败条件,因为t1它只是落后了。

现在,也就是说,在更广泛的上下文中,它并不是特别安全,就好像用户创建了一个weak_ptr,完全有可能use_count在不通过此函数的情况下从 1 变为 2 。在这个简单的示例中,可以让它循环遍历索引数组,直到找到空闲的共享指针。

  • 另一个指针保存在 Circular 内部的 std::array&lt;std::shared_ptr&lt;int&gt;, 2&gt; 内,即每个 std::shared_ptr&lt;int&gt; 上的“真实”引用计数应始终至少为1 因为 std::array 内部始终保存一个这样的共享指针。 (2认同)