C++ std::shared_ptr 递增和递减引用计数的竞赛

标签 c++ multithreading std shared-ptr

来自the reference我知道std::shared_ptr<T>本身是线程安全的,因为引用计数通常由一些std::atomic来实现与 std::memory_order_relaxed .

但是我还是不知道怎么办std::shared_ptr确保引用计数器并发递增和递减下的线程安全。

即,

Thread 1:
// destructing shared_ptr<T>
// rc.fetch_sub(1)
// Thread 1 got RC == 0 and *thought* that he is the last owner
// ** call T::~T()

Thread 2:
// concurrently copy that shared_ptr<T>
// rc.fetch_add(1)
// Thread 2 got RC == 1 and *thought* that the copied shared_ptr is valid
// dereference the shared_ptr
// ** accessing the destructed T !

这种情况虽然存在种族问题,但并非不可能。 下面是示例代码(手动构造一个极端情况)。

std::shared_ptr<T> ptr;
int main()
{
    std::thread t([&ptr] () 
    {
        ptr = std::make_shared<int>();
    } // reference counting decrease here! Call ~T()
    );

    auto ptr2 = ptr; // reference counting increase here! 
    ptr2->some_function(); // access destructed object!

    t.join();
}

我的问题是:

  • C++ 引用文献如何描述此案例?
  • std::shared_ptr<T>即使在引用计数器并发递增和递减的情况下也能确保线程安全吗?

最佳答案

shared_ptr 内部不使用 fetch_add 等,而是使用 compare_exchange。来自 GNU 实现:

  template<>
    inline bool
    _Sp_counted_base<_S_atomic>::
    _M_add_ref_lock_nothrow() noexcept
    {
      // Perform lock-free add-if-not-zero operation.
      _Atomic_word __count = _M_get_use_count();
      do
        {
          if (__count == 0)
            return false;
          // Replace the current counter value with the old value + 1, as
          // long as it's not changed meanwhile.
        }
      while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count + 1,
                                          true, __ATOMIC_ACQ_REL,
                                          __ATOMIC_RELAXED));
      return true;
    }

有一些样板文件,但本质上它的作用是获取当前计数,检查它是否为零,否则在读取后该值没有更改的预期下执行原子compare_exchange操作。这是无锁(但不是无等待)数据结构中相当常见的机制。如果你稍微眯一下眼睛,你可以将其称为一种用户空间自旋锁。与互斥锁相比,这是有利的,因为它可以节省非常昂贵的系统调用,除非对shared_ptr的争用非常高。

顺便说一句,您的代码应该在使用前检查 ptr2 是否有效,因为线程可能在主线程中的复制之前结束。但这似乎与您的实际问题无关。

关于C++ std::shared_ptr 递增和递减引用计数的竞赛,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/74364000/

相关文章:

Java 同步类实例

c++ - 为什么在 shared_ptr 只取一个时 unique_ptr 取两个模板参数?

c++ - cmake,无法将静态库链接到共享库

c# - C#-如何在ASP.NET应用程序中处理子线程

C++ Const 函数参数或声明中的正确性

c++ - 在 C++ 异常的 what() 中构建字符串

C++ RFC3339 时间戳与毫秒使用 std::chrono

c++ - 当列表为空时 std::list:begin() 的行为

c++ - 使用构造函数重新定义

c++ - 链接器如何知道两个源文件中哪个是主文件,哪个包含函数定义?