0%

shared_ptr 的实现

参考源码

/usr/include/c++/11/bits/shared_ptr_base.h
/usr/include/c++/11/bits/shared_ptr.h

博客:https://zhiqiang.org/coding/std-shared-ptr.html

std::shared_ptr 的性质

  1. 复制构造、析构是线程安全的。

标准库使用原子操作实现无锁的线程安全性。

  1. 写操作(例如 reset 、 赋值 operator= )是线程不安全的。

写操作和复制构造、析构的主要区别是:

  • 复制构造、析构函数中,单个线程只处理一个对象,复制构造函数将其他对象复制过来之后,不会改动其他对象的资源(引用计数、所管理的内存)。
  • 但是写操作可能多个线程都在处理该 shared_ptr 。例如多个线程都对同一个 shared_ptr 进行赋值:
1
2
3
shared_ptr<int> sp1 = make_shared<int>(1);
sp1 = sp2; // 线程 1
sp1 = sp3; // 线程 2

对比源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
     __shared_count&
operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != nullptr)
__tmp->_M_add_ref_copy();
if (_M_pi != nullptr)
_M_pi->_M_release(); // 注意:线程 1 和线程 2 持有相同的 _M_pi!
_M_pi = __tmp; // 注意:线程 1 和线程 2 持有相同的 _M_pi!
}
return *this;
}

以上代码中两个“注意”可能同时在发生,例如:
线程 1 release 的时候,线程 2 在给 _M_pi 赋值;
或者两个线程同时在 release 或同时在给 _M_pi 赋值。

要点

  1. 管理的内存和引用计数都应该动态分配到堆( heap )上,这样多个 shared_ptr 对象才能更新同一份数据。
  2. 需要同时维护强引用计数和弱引用计数。
  3. 引用计数本身应该是一个控制块类,使用 delete this 来自动删除(析构)引用计数。

永远不要手动 delete use_count,因为其他线程可能此时正在使用该资源,例如解引用 *use_count

SharedPtr.hppview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
template<_Lock_policy _Lp = __default_lock_policy>
class _Sp_counted_base
: public _Mutex_base<_Lp>
{
void
_M_add_ref_copy()
{ __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); }

void
_M_release() noexcept
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_use_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
_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();
}
}
}

private:
_Sp_counted_base(_Sp_counted_base const&) = delete;
_Sp_counted_base& operator=(_Sp_counted_base const&) = delete;

// using _Atomic_word = int;
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count; // #weak + (#shared != 0)
};

template<_Lock_policy _Lp>
class __shared_count
{
_Sp_counted_base<_Lp>* _M_pi; // 引用计数的指针

__shared_count(const __shared_count& __r) noexcept
: _M_pi(__r._M_pi)
{
if (_M_pi != nullptr)
_M_pi->_M_add_ref_copy(); // TODO: 有没有可能此时的 _M_pi 所引对象已经被释放了呢
}

~__shared_count() noexcept
{
if (_M_pi != nullptr)
_M_pi->_M_release(); // 没有直接 delete _M_pi,
// 而是使用 _M_pi 的成员函数来释放它。
}

__shared_count&
operator=(const __shared_count& __r) noexcept
{
_Sp_counted_base<_Lp>* __tmp = __r._M_pi;
if (__tmp != _M_pi)
{
if (__tmp != nullptr)
__tmp->_M_add_ref_copy();
if (_M_pi != nullptr)
_M_pi->_M_release(); // 没有直接 delete _M_pi,
// 而是使用 _M_pi 的成员函数来释放它。
_M_pi = __tmp;
}
return *this;
}
};

参考

https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_concurrency.html