关于 libstdc++ 中 _Lock_policy 的困惑

Confusion about _Lock_policy in libstdc++

我正在阅读 libstdc++ std::shared_ptr 的实现,我注意到 libstdc++ 具有三种锁定策略:_S_single、_S_mutex 和 _S_atomic(请参阅here), and the lock policy would affect the specialization of class _Sp_counted_base (_M_add_ref and _M_release)

下面是代码片段:

_M_release_last_use() noexcept
{
    _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_use_count);
    _M_dispose();
    // There must be a memory barrier between dispose() and destroy()
    // to ensure that the effects of dispose() are observed in the
    // thread that runs destroy().
    // See http://gcc.gnu.org/ml/libstdc++/2005-11/msg00136.html
    if (_Mutex_base<_Lp>::_S_need_barriers)
    {
        __atomic_thread_fence (__ATOMIC_ACQ_REL);
    }

    // Be race-detector-friendly.  For more info see bits/c++config.
    _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
    if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count,
                                               -1) == 1)
    {
        _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
        _M_destroy();
    }
  }
 
  template<>
  inline bool
  _Sp_counted_base<_S_mutex>::
  _M_add_ref_lock_nothrow() noexcept
  {
     __gnu_cxx::__scoped_lock sentry(*this);
    if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, 1) == 0)
    {
         _M_use_count = 0;
         return false;
     }
     return true;
   }

我的问题是:

  1. 使用_S_mutex锁策略时,__exchange_and_add_dispatch函数可能只能保证原子性,但不一定保证完全fenced,对吗?
  2. 并且由于 1,'__atomic_thread_fence (__ATOMIC_ACQ_REL)' 的目的是确保当线程 A 调用 _M_dispose 时,没有线程会调用 _M_destory (这样线程 A 就永远无法访问函数 '_M_dispose' 中被破坏的成员(例如:_M_ptr)?
  3. 最让我不解的是,如果1和2都是正确的,那为什么在调用'_M_dispose'之前不需要添加线程栅栏? (因为_Sp_counted_base管理的对象和_Sp_counted_base本身在引用计数降为0的时候有同样的问题)

其中一些在 documentation 和您引用的代码中显示的 URL 中得到了回答。

  1. When using the _S_mutex locking policy, the __exchange_and_add_dispatch function may only guarantee atomicity, but may not guarantee that it is fully fenced, am I right?

是的。

  1. and because of 1, is the purpose of '__atomic_thread_fence (__ATOMIC_ACQ_REL)' to ensure that when thread A invoking _M_dispose, no thread will invoke _M_destory (so that thread A could never access a destroyed member(eg: _M_ptr) inside the function '_M_dispose'?

不,这不可能发生。当_M_release_last_use()开始执行_M_weak_count >= 1时,_M_destroy()函数直到_M_weak_count递减后才会被调用,所以_M_ptr在[=15]期间一直有效=] 打电话。如果 _M_weak_count==2 和另一个线程在 _M_release_last_use() 运行s 的同时递减它,那么就会竞争看哪个线程先递减,所以你不知道哪个线程将 运行 _M_destroy()。但是无论是哪个线程,_M_dispose()_M_release_last_use() 递减之前就已经完成了。

内存屏障的原因是_M_dispose()调用删除器,运行是用户定义的代码。该代码可能具有任意副作用,触及程序中的其他对象。 _M_destroy() 函数使用 operator delete 或分配器释放内存,这也可能产生任意副作用(如果程序替换了 operator delete,或者 shared_ptr 是用自定义分配器)。内存屏障确保删除器的效果与释放的效果同步。库不知道这些效果是什么,但没关系,代码仍然确保它们同步。

  1. What puzzles me most is that if 1 and 2 are both correct,

他们不是。