【问题标题】:Confusion about _Lock_policy in libstdc++关于 libstdc++ 中的 _Lock_policy 的困惑
【发布时间】:2022-01-14 06:18:02
【问题描述】:

我正在阅读 libstdc++ 的 std::shared_ptr 的实现,我注意到 libstdc++ 具有三个锁定策略:_S_single、_S_mutex 和 _S_atomic(请参阅here),并且锁定策略会影响类的专业化_Sp_counted_base(_M_add_ref_M_release

下面是sn-p的代码:

_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 函数可能只保证原子性,但不能保证完全隔离,对吗?
  2. 因为 1,'__atomic_thread_fence (__ATOMIC_ACQ_REL)' 的目的是确保当线程 A 调用 _M_dispose 时,没有线程将调用 _M_destory(这样线程 A 永远无法访问被破坏的成员(例如:_M_ptr)函数“_M_dispose”?
  3. 最让我困惑的是,如果 1 和 2 都正确,那为什么在调用 '_M_dispose' 之前不需要添加线程栅栏呢? (因为 _Sp_counted_base 和 _Sp_counted_base 管理的对象在引用计数降为零时本身也有同样的问题)

【问题讨论】:

  • 没有glibc++这种东西
  • @Jonathan Wakely 对不起,应该是 libstdc++,我更正了

标签: c++ multithreading std libstdc++ memory-barriers


【解决方案1】:

documentation 和您引用的代码中显示的 URL 已回答了其中的一些问题。

  1. 使用 _S_mutex 锁定策略时,__exchange_and_add_dispatch 函数可能只保证原子性,但不能保证完全隔离,对吗?

是的。

  1. 因为 1,'__atomic_thread_fence (__ATOMIC_ACQ_REL)' 的目的是确保当线程 A 调用 _M_dispose 时,没有线程将调用 _M_destory(这样线程 A 永远无法访问被破坏的成员(例如:_M_ptr)函数“_M_dispose”?

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

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

  1. 最让我困惑的是,如果1和2都正确,

他们不是。

【讨论】:

  • 知道了,非常感谢。所以不需要在_S_atomic下添加thread_fence,因为__exchange_and_add_dispatch(&amp;_M_weak_count)已经保证了_M_dispose的可见性?
  • 是的,没错。
  • 另一个问题,'_Sp_counted_base' 如何保证 _S_mutex 策略下 'exchange_and_add' 周围的 'SYNC_HAPPENS_BEFORE' 关系? AFK,原子引用计数器在减少引用计数器时应该添加 ACQ/REL 内存栅栏。
  • 这不是__atomic_thread_fence 的用途吗?我真的不在乎,我认为没有任何目标仍然使用_S_mutex 策略。所有目标现在都支持 _Atomic_word 上的原子内置函数。
  • 好吧,还有一点我不明白,为什么_M_add_ref_lock在compare_exchange_n返回true时指定了__ATOMIC_ACQ_REL? related question
猜你喜欢
  • 2022-01-07
  • 2019-05-17
  • 2020-03-06
  • 2016-03-16
  • 2013-07-27
  • 1970-01-01
  • 2012-09-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多