关于 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;
}
我的问题是:
- 使用_S_mutex锁策略时,__exchange_and_add_dispatch函数可能只能保证原子性,但不一定保证完全fenced,对吗?
- 并且由于 1,'__atomic_thread_fence (__ATOMIC_ACQ_REL)' 的目的是确保当线程 A 调用 _M_dispose 时,没有线程会调用 _M_destory (这样线程 A 就永远无法访问函数 '_M_dispose' 中被破坏的成员(例如:_M_ptr)?
- 最让我不解的是,如果1和2都是正确的,那为什么在调用'_M_dispose'之前不需要添加线程栅栏? (因为_Sp_counted_base管理的对象和_Sp_counted_base本身在引用计数降为0的时候有同样的问题)
其中一些在 documentation 和您引用的代码中显示的 URL 中得到了回答。
- 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?
是的。
- 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
是用自定义分配器)。内存屏障确保删除器的效果与释放的效果同步。库不知道这些效果是什么,但没关系,代码仍然确保它们同步。
- What puzzles me most is that if 1 and 2 are both correct,
他们不是。
我正在阅读 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;
}
我的问题是:
- 使用_S_mutex锁策略时,__exchange_and_add_dispatch函数可能只能保证原子性,但不一定保证完全fenced,对吗?
- 并且由于 1,'__atomic_thread_fence (__ATOMIC_ACQ_REL)' 的目的是确保当线程 A 调用 _M_dispose 时,没有线程会调用 _M_destory (这样线程 A 就永远无法访问函数 '_M_dispose' 中被破坏的成员(例如:_M_ptr)?
- 最让我不解的是,如果1和2都是正确的,那为什么在调用'_M_dispose'之前不需要添加线程栅栏? (因为_Sp_counted_base管理的对象和_Sp_counted_base本身在引用计数降为0的时候有同样的问题)
其中一些在 documentation 和您引用的代码中显示的 URL 中得到了回答。
- 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?
是的。
- 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
是用自定义分配器)。内存屏障确保删除器的效果与释放的效果同步。库不知道这些效果是什么,但没关系,代码仍然确保它们同步。
- What puzzles me most is that if 1 and 2 are both correct,
他们不是。