浅析 RefCounted 和 WeakPtr:Chromium Base 篇

序言请移步此处

MSVC STL 的分析版本请移步此处

Libstdc++ 的分析版本请移步此处

Boost 的分析版本请移步此处

注 1:因为这不是第一篇分析,所以会直入主题,跳过文学写作常用的累赘的过渡。

注 2:这是系列最后一篇。

目标版本选择

Chromium tag 68.0.3421.1

代码位置:base/memory/ref_counted.{h, cc}

RefCountedBase 和 RefCounted

两个类实现了非线程安全的引用计数,即:内部计数使用的是 built-in integer

先看看 RefCountedBase 的大致结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class RefCountedBase {
protected:
explicit RefCountedBase(StartRefCountFromZeroTag);

explicit RefCountedBase(StartRefCountFromOneTag);

~RefCountedBase();

void AddRef() const;

bool Release() const;

private:
mutable uint32_t ref_count_ = 0;

#if DCHECK_IS_ON()
mutable bool needs_adopt_ref_ = false;
mutable bool in_dtor_ = false;
mutable SequenceChecker sequence_checker_;
#endif

DISALLOW_COPY_AND_ASSIGN(RefCountedBase);
};

可以看出核心 ref_count_ 类型是 uint32_t

ctor 和 dtor 都被定义为 protected,说明这类使用做基类;同时提供了 AddRef()Release(),进行内部的计数增减。

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
void AddRef() const {
#if DCHECK_IS_ON()
DCHECK(!in_dtor_);
DCHECK(!needs_adopt_ref_)
<< "This RefCounted object is created with non-zero reference count."
<< " The first reference to such a object has to be made by AdoptRef or"
<< " MakeRefCounted.";
if (ref_count_ >= 1) {
DCHECK(CalledOnValidSequence());
}
#endif
 
AddRefImpl();
}
 
void RefCountedBase::AddRefImpl() const {
// Check if |ref_count_| overflow only on 64 bit archs since the number of
// objects may exceed 2^32.
// To avoid the binary size bloat, use non-inline function here.
CHECK(++ref_count_ > 0);
}

// Returns true if the object should self-delete. 
bool Release() const {
--ref_count_;
 
#if DCHECK_IS_ON()
DCHECK(!in_dtor_);
if (ref_count_ == 0)
in_dtor_ = true;
 
if (ref_count_ >= 1)
DCHECK(CalledOnValidSequence());
if (ref_count_ == 1)
sequence_checker_.DetachFromSequence();
#endif
 
return ref_count_ == 0;
}

两个函数除了计数增减外,还有相当多用于调试检查的代码。

吐槽一句,这里把两个函数标记为 const,同时使用 mutable 成员的操作真是…

接下来看一下 RefCounted

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
template <typename T>
struct DefaultRefCountedTraits {
static void Destruct(const T* x) {
RefCounted<T, DefaultRefCountedTraits>::DeleteInternal(x);
}
};
 
template <class T, typename Traits = DefaultRefCountedTraits<T>>
class RefCounted : public subtle::RefCountedBase {
public:
static constexpr subtle::StartRefCountFromZeroTag kRefCountPreference =
subtle::kStartRefCountFromZeroTag;
 
RefCounted() : subtle::RefCountedBase(T::kRefCountPreference) {}
 
void AddRef() const {
subtle::RefCountedBase::AddRef();
}
 
void Release() const {
if (subtle::RefCountedBase::Release()) {
// Prune the code paths which the static analyzer may take to simulate
// object destruction. Use-after-free errors aren't possible given the
// lifetime guarantees of the refcounting system.
ANALYZER_SKIP_THIS_PATH();
 
Traits::Destruct(static_cast<const T*>(this));
}
}
 
protected:
~RefCounted() = default;
 
private:
friend struct DefaultRefCountedTraits<T>;
 
template <typename U>
static void DeleteInternal(const U* x) {
delete x;
}
 
DISALLOW_COPY_AND_ASSIGN(RefCounted);
};

这个实现可以算是 CRTP 的经典实现,毕竟父类要正确的 delete 子类必须要保证操作能拿到子类的实际类型。

具体的 deletion 可以通过模板参数注入,默认的策略就是使用 delete this

还有一点需要注意一下:RefCounted 的构造函数结束后,计数仍然是0,除非子类修改了 preferred start-count;这意味着 scoped_refptr 一定会在构造时候手动调用 AddRef(),这点后面可以看到。

接下来看一下线程安全的 RefCountedThreadSafeBase

1
2
3
4
5
6
7
8
9
10
mutable AtomicRefCount ref_count_{0};
 
// for AddRef()
ref_count_.Increment();
 
// for Release()
if (!ref_count_.Decrement()) {
return true;
}
return false;

这个版本的 AtomicRefCount 内部实现是 std::atomic_int,代码位置在 base/atomic_ref_count.h

因为 RefCountedThreadSafe 实现基本和 RefCounted 一致,这里就不细究了。

Ref-count Start Policy

RefCounted / RefCountedThreadSafe 初始化他们的父类时,会传入 kRefCountPreference,这个值指明引用计数应该从0还是1开始。这个值可以被子类重定义。

对比 shared_ptr

base 的 RefCounted(ThreadSafe) 是侵入式的实现,并且 deleter 的提供是直接作为类型一部分。

另外,因为侵入式,所以不需要 std::enable_shared_from_this 这类东西了。

RAII for Reference Counting: scoped_refptr

scoped_refptr 在这里完全就是一个引用计数操作的 RAII 设施

1
2
3
4
5
6
7
8
9
10
template <class T>
class scoped_refptr {
public:
typedef T element_type;
 
// omitted
 
protected:
T* ptr_ = nullptr;
};

它的 AddRef()Release() 被定义为了 static functions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Non-inline helpers to allow:
// class Opaque;
// extern template class scoped_refptr<Opaque>;
// Otherwise the compiler will complain that Opaque is an incomplete type.
static void AddRef(T* ptr);
static void Release(T* ptr);
 
// static
template <typename T>
void scoped_refptr<T>::AddRef(T* ptr) {
ptr->AddRef();
}
 
// static
template <typename T>
void scoped_refptr<T>::Release(T* ptr) {
ptr->Release();
}

类似的,这里有两种方式创建一个 scoped_refptr 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
// The old-fashioned way
scoped_refptr<MyFoo> foo(new MyFoo());
 
// The make-way
scoped_refptr<MyFoo> foo = MakeRefCounted<MyFoo>();

we check the old-fashioned way first, examing what it will do.

// Constructs from raw pointer. constexpr if |p| is null.
constexpr scoped_refptr(T* p) : ptr_(p) {
if (ptr_)
AddRef(ptr_);
}

先看看传统方式:

1
2
3
4
5
// Constructs from raw pointer. constexpr if |p| is null.
constexpr scoped_refptr(T* p) : ptr_(p) {
if (ptr_)
AddRef(ptr_);
}

做了两件事:赋值 + 加计数

接下来看一下新版的 MakeRefCounted()

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
// Constructs an instance of T, which is a ref counted type, and wraps the
// object into a scoped_refptr<T>.
template <typename T, typename... Args>
scoped_refptr<T> MakeRefCounted(Args&&... args) {
T* obj = new T(std::forward<Args>(args)...);
return subtle::AdoptRefIfNeeded(obj, T::kRefCountPreference);
}
 
namespace subtle {
 
template <typename T>
scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromZeroTag) {
return scoped_refptr<T>(obj);
}
 
template <typename T>
scoped_refptr<T> AdoptRefIfNeeded(T* obj, StartRefCountFromOneTag) {
return AdoptRef(obj);
}
 
} // namespace subtle
 
// Creates a scoped_refptr from a raw pointer without incrementing the reference
// count. Use this only for a newly created object whose reference count starts
// from 1 instead of 0.
template <typename T>
scoped_refptr<T> AdoptRef(T* obj) {
using Tag = std::decay_t<decltype(T::kRefCountPreference)>;
static_assert(std::is_same<subtle::StartRefCountFromOneTag, Tag>::value,
"Use AdoptRef only for the reference count starts from one.");
 
DCHECK(obj);
DCHECK(obj->HasOneRef());
obj->Adopted();
return scoped_refptr<T>(obj, subtle::kAdoptRefTag);
}
 
scoped_refptr(T* p, base::subtle::AdoptRefTag) : ptr_(p)
{}

这种方式是 ref-count-start-policy aware,如果选择计数从1开始,那么构造时就不会自增。

相比较而言,这也是 preferable way

剩下的最重要的点就是复制和移动,毕竟 ref-counted 最重要的就是计数相关的语义:

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
// Copy constructor. This is required in addition to the copy conversion
// constructor below.
scoped_refptr(const scoped_refptr& r) : scoped_refptr(r.ptr_)
{}
 
// Copy conversion constructor.
template <typename U,
typename = typename std::enable_if<
std::is_convertible<U*, T*>::value>::type>
scoped_refptr(const scoped_refptr<U>& r) : scoped_refptr(r.ptr_)
{}
 
// Move constructor. This is required in addition to the move conversion
// constructor below.
scoped_refptr(scoped_refptr&& r) noexcept : ptr_(r.ptr_) {
r.ptr_ = nullptr;
}
 
// Move conversion constructor.
template <typename U,
typename = typename std::enable_if<
std::is_convertible<U*, T*>::value>::type>
scoped_refptr(scoped_refptr<U>&& r) noexcept : ptr_(r.ptr_) {
r.ptr_ = nullptr;
}
 
scoped_refptr& operator=(T* p) {
return *this = scoped_refptr(p);
}
 
// Unified assignment operator.
scoped_refptr& operator=(scoped_refptr r) noexcept {
swap(r);
return *this;
}

base 提供了诸如 WeakPtr 允许使用者检查一个对象是否已经析构(毕竟千古难题)

但是和 std::weak_ptr 依赖 std::shared_ptr 不一样,WeakPtrRefCounted 是(逻辑上)互相独立的

常规用法如下:

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
class Foo {
public:
explicit Foo(std::string s)
: str_(std::move(s)), weak_factory_(this)
{}
 
~Foo() = default;
 
const std::string& str() const noexcept
{
return str_;
}
 
base::WeakPtr<Foo> as_weak_ptr()
{
return weak_factory_.GetWeakPtr();
}
 
private:
std::string str_;
base::WeakPtrFactory<Foo> weak_factory_;
};
 
base::WeakPtr<Foo> ptr;
 
{
Foo f("test");
ptr = f.as_weak_ptr();
if (ptr) {
std::cout << ptr->str() << std::endl;
}
}
 
if (!ptr) {
std::cout << "The foo has been dead\n";
}

先从 WeakPtrFactory 入手:

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
namespace internal {
class BASE_EXPORT WeakPtrFactoryBase {
protected:
WeakPtrFactoryBase(uintptr_t ptr)
: ptr_(ptr)
{}
 
~WeakPtrFactoryBase() {
ptr_ = 0;
}
 
internal::WeakReferenceOwner weak_reference_owner_;
uintptr_t ptr_;
};
} // namespace internal
 
template <class T>
class WeakPtrFactory : public internal::WeakPtrFactoryBase {
public:
explicit WeakPtrFactory(T* ptr) // Note: ptr here usually is this-ptr.
: WeakPtrFactoryBase(reinterpret_cast<uintptr_t>(ptr)) {}
 
~WeakPtrFactory() = default;
 
WeakPtr<T> GetWeakPtr() {
DCHECK(ptr_);
return WeakPtr<T>(weak_reference_owner_.GetRef(),
reinterpret_cast<T*>(ptr_));
}
 
// Omitted...
 
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);
};

WeakPtrFactory 没有直接定义任何成员,唯二的两个定义在父类 WeakPtrFactoryBase中:

  • ptr_ 保存了管理对象的指针
  • weak_reference_owner_ 用来跟踪对象是否还活着

所以看一下 WeakReferenceOwner

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
class BASE_EXPORT WeakReferenceOwner {
public:
WeakReferenceOwner() = default;
 
~WeakReferenceOwner();
 
WeakReference GetRef() const;
 
bool HasRefs() const { return flag_ && !flag_->HasOneRef(); }
 
void Invalidate();
 
private:
mutable scoped_refptr<WeakReference::Flag> flag_;
};
 
WeakReferenceOwner::~WeakReferenceOwner() {
Invalidate();
}
 
WeakReference WeakReferenceOwner::GetRef() const {
// If we hold the last reference to the Flag then create a new one.
if (!HasRefs())
flag_ = new WeakReference::Flag();
 
return WeakReference(flag_);
}
 
void WeakReferenceOwner::Invalidate() {
if (flag_) {
flag_->Invalidate();
flag_ = nullptr;
}
}
 
// An inner class of WeakReference
class BASE_EXPORT Flag : public RefCountedThreadSafe<Flag> {
public:
Flag();
 
void Invalidate();
bool IsValid() const;
 
private:
friend class base::RefCountedThreadSafe<Flag>;
 
~Flag();
 
SequenceChecker sequence_checker_;
bool is_valid_;
};

WeakReferenceOwner 内部维护了 WeakReference::Flag,which 是一个引用计数对象,并且 WeakReferenceOwner 可以通过这个 flag 创建 WeakReference 对象,这个就是 WeakReferenceOwner::GetRef() 做的事儿。

所以我们可以提出一个假设:所有相关的 WeakReference 内部都有一个相同的 flag,并且这个 flag 由一个 WeakReferenceOwner 控制:如果 invalidate 这个 flag,比如 WeakReferenceOwner 进入析构了,那么所有的 WeakReference 都能知道他们关心的对象要狗带了。

为了证实这个假设,我们看一下 WeakReference 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BASE_EXPORT WeakReference {
public:
WeakReference() = default;
explicit WeakReference(const scoped_refptr<Flag>& flag);
~WeakReference() = default;
 
WeakReference(WeakReference&& other) = default;
WeakReference(const WeakReference& other) = default;
WeakReference& operator=(WeakReference&& other) = default;
WeakReference& operator=(const WeakReference& other) = default;
 
bool is_valid() const;
 
private:
scoped_refptr<const Flag> flag_;
};
 
WeakReference::WeakReference(const scoped_refptr<Flag>& flag) : flag_(flag)
{}
 
bool WeakReference::is_valid() const {
return flag_ && flag_->IsValid();
}

从上面的实现可以看出,WeakReference 实例实际上代表对 flag 的一个引用访问。

接下来我们看一下 WeakPtr 是怎么把这些东西组合在一起的:

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
82
class BASE_EXPORT WeakPtrBase {
public:
WeakPtrBase();
~WeakPtrBase();
 
WeakPtrBase(const WeakPtrBase& other) = default;
WeakPtrBase(WeakPtrBase&& other) = default;
WeakPtrBase& operator=(const WeakPtrBase& other) = default;
WeakPtrBase& operator=(WeakPtrBase&& other) = default;
 
void reset() {
ref_ = internal::WeakReference();
ptr_ = 0;
}
 
protected:
WeakPtrBase(const WeakReference& ref, uintptr_t ptr);
 
WeakReference ref_;
 
// This pointer is only valid when ref_.is_valid() is true. Otherwise, its
// value is undefined (as opposed to nullptr).
uintptr_t ptr_;
};
 
WeakPtrBase::WeakPtrBase() : ptr_(0) {}
 
WeakPtrBase::~WeakPtrBase() = default;
 
WeakPtrBase::WeakPtrBase(const WeakReference& ref, uintptr_t ptr)
: ref_(ref), ptr_(ptr)
{}
 
template <typename T>
class WeakPtr : public internal::WeakPtrBase {
public:
WeakPtr() = default;
 
WeakPtr(std::nullptr_t) {}
 
// Allow conversion from U to T provided U "is a" T. Note that this
// is separate from the (implicit) copy and move constructors.
template <typename U>
WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other) {
// Need to cast from U* to T* to do pointer adjustment in case of multiple
// inheritance. This also enforces the "U is a T" rule.
T* t = reinterpret_cast<U*>(other.ptr_);
ptr_ = reinterpret_cast<uintptr_t>(t);
}
template <typename U>
WeakPtr(WeakPtr<U>&& other) : WeakPtrBase(std::move(other)) {
// Need to cast from U* to T* to do pointer adjustment in case of multiple
// inheritance. This also enforces the "U is a T" rule.
T* t = reinterpret_cast<U*>(other.ptr_);
ptr_ = reinterpret_cast<uintptr_t>(t);
}
 
T* get() const {
return ref_.is_valid() ? reinterpret_cast<T*>(ptr_) : nullptr;
}
 
T& operator*() const {
DCHECK(get() != nullptr);
return *get();
}
T* operator->() const {
DCHECK(get() != nullptr);
return get();
}
 
// Allow conditionals to test validity, e.g. if (weak_ptr) {...};
explicit operator bool() const { return get() != nullptr; }
 
private:
friend class internal::SupportsWeakPtrBase;
template <typename U> friend class WeakPtr;
friend class SupportsWeakPtr<T>;
friend class WeakPtrFactory<T>;
 
WeakPtr(const internal::WeakReference& ref, T* ptr)
: WeakPtrBase(ref, reinterpret_cast<uintptr_t>(ptr)) {}
};

WeakPtr 有两个成员:

  • ref_ 来反映被管理对象的生命周期
  • ptr_ 保存被管理对象的地址,仅当 ref_ 有效时这个成员才是可以安全访问的

Conclusion

这部分实现和 std::weak_ptr 颇有类似,核心都是通过:被多个探测器(WeakPtr)共享的一个引用技术控制块,这里是 WeakReference::Flag,来保存被管理对象的状态,因为控制块的生命周期可以远超过对象本身,因此即使对象狗带了,这个控制块也可以被访问到。

当对象行将就木之际,控制块设置为 invalid 状态,这样 WeakPtr::get() 就能探测到对象已经挂了,返回 nullptr。

base 版本的实现的最大的缺点是:这部分不是线程安全的。

不过有时候这也是一个优点,强迫规范线程的使用,同时结合 CSP 来尽可能促进一个对象的生老病死只发生在一个县城。

Epilogue

这篇 post 是整个系列的最后一篇了,这四篇分析过来基本也可以对 reference counting 以及 weak-reference idiom 有了一定的了解。

从这几篇 post 的源代码可以看出,Chromium 的 C++ 写的并不好,但是工程化做的很漂亮。有时候可能这点更加重要。

浅析 shared_ptr:Boost 篇

序言请移步此处

MSVC STL 的分析版本请移步此处

Libstdc++ 的分析版本请移步此处

注:因为这不是第一篇分析,所以会直入主题,跳过文学写作常用的累赘的过渡。

目标版本选择

选用最新的 Boost 1.67 作为研究目标

在开始正题前,先简单看一下 shared_ptr 的类成员,方便后续分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace detail {

template< class T > struct sp_element
{
typedef T type;
};

}

template<class T>
class shared_ptr {
public:
typedef typename boost::detail::sp_element< T >::type element_type;

// omitted other

private:
element_type * px; // contained pointer
boost::detail::shared_count pn; // reference counter
};

How shared_ptr(new T()) differs from make_shared()

同样的,先看 make_shared(),位于 boost/smart_ptr/make_shared_object.hpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template< class T, class... Args > typename boost::detail::sp_if_not_array< T >::type make_shared( Args && ... args )
{
boost::shared_ptr< T > pt( static_cast< T* >( 0 ), BOOST_SP_MSD( T ) );

boost::detail::sp_ms_deleter< T > * pd = static_cast<boost::detail::sp_ms_deleter< T > *>( pt._internal_get_untyped_deleter() );

void * pv = pd->address();

::new( pv ) T( boost::detail::sp_forward<Args>( args )... );
pd->set_initialized();

T * pt2 = static_cast< T* >( pv );

boost::detail::sp_enable_shared_from_this( &pt, pt2, pt2 );

return boost::shared_ptr< T >( pt, pt2 );
}

这部分代码罕见的异常复杂…

首先创建了一个空 shared_ptr 实例 pt,调用的是构造函数

1
2
3
template<class D>
shared_ptr( boost::detail::sp_nullptr_t p, D d ): px( p ), pn( p, d )
{}

因为 p 已经是 nullptr,因此这部分的核心是利用自定义的 deleter BOOST_SP_MSD(T) 来初始化计数控制块 pn

BOOST_SP_MSD(T) 的定义是

1
2
3
4
# define BOOST_SP_MSD( T ) boost::detail::sp_inplace_tag< boost::detail::sp_ms_deleter< T > >()

template< class D > struct sp_inplace_tag
{};

boost::detail::shared_count 这个我们之前在分析 libstdc++ 版本的代码里也有遇到过。这个类的成员很简单:

1
2
3
4
5
6
7
8
9
10
11
12
class shared_count
{
private:

sp_counted_base * pi_;

#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
int id_;
#endif

// omitted
};

接下来看一下 boost::detail::shared_count 的构造相关的过程。

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
template< class P, class D > shared_count( P p, sp_inplace_tag<D> ): pi_( 0 )
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
try
{
// Note: extract deleter type D and use its default contructor.
pi_ = new sp_counted_impl_pd< P, D >( p );
}
catch( ... )
{
D::operator_fn( p ); // delete p
throw;
}
}

template<class P, class D> class sp_counted_impl_pd: public sp_counted_base
{
private:

P ptr; // copy constructor must not throw
D del; // copy constructor must not throw

sp_counted_impl_pd( sp_counted_impl_pd const & );
sp_counted_impl_pd & operator= ( sp_counted_impl_pd const & );

typedef sp_counted_impl_pd<P, D> this_type;

public:

// pre: d(p) must not throw

sp_counted_impl_pd( P p, D & d ): ptr( p ), del( d )
{
}

// Deleter is default constructed.
sp_counted_impl_pd( P p ): ptr( p ), del()
{
}
};

可以看出,前面一顿操作猛如虎(shared_ptr -> shared_count -> sp_counted_impl_pd),最后的结果就是创建了一个默认构造的 boost::detail::sp_ms_deleter<T>,于是我们得看一下 sp_ms_deleter 这类的结构和几个相关的操作:

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
template< class T > class sp_ms_deleter
{
private:

typedef typename sp_aligned_storage< sizeof( T ), ::boost::alignment_of< T >::value >::type storage_type;

bool initialized_;

// NOTE: storage for managed data in type T
storage_type storage_;

private:

void destroy() BOOST_SP_NOEXCEPT
{
if( initialized_ )
{
#if defined( __GNUC__ )

// fixes incorrect aliasing warning
T * p = reinterpret_cast< T* >( storage_.data_ );
p->~T();

#else

reinterpret_cast< T* >( storage_.data_ )->~T();

#endif

initialized_ = false;
}
}

public:

sp_ms_deleter() BOOST_SP_NOEXCEPT : initialized_( false )
{
}

template<class A> explicit sp_ms_deleter( A const & ) BOOST_SP_NOEXCEPT : initialized_( false )
{
}

// optimization: do not copy storage_
sp_ms_deleter( sp_ms_deleter const & ) BOOST_SP_NOEXCEPT : initialized_( false )
{
}

~sp_ms_deleter() BOOST_SP_NOEXCEPT
{
destroy();
}

void operator()( T * ) BOOST_SP_NOEXCEPT
{
destroy();
}

static void operator_fn( T* ) BOOST_SP_NOEXCEPT // operator() can't be static
{
}

void * address() BOOST_SP_NOEXCEPT
{
return storage_.data_;
}

void set_initialized() BOOST_SP_NOEXCEPT
{
initialized_ = true;
}
};

这里保存管理对象的方式和 MSVC STL 一样,都是使用了 aligned_storage 直接在构造 deleter 对象时一同分配好对象的存储空间。

剩下的就是让外部访问到这块空间的地址,并利用 placement new 进行对象的构造;这也就是上面这几段代码做的事情。

另外注意这个类的基类,sp_counted_base 保存了引用计数成员。

接下来的 enable-shared-from-this 是常规操作,暂时先跳过,看一下最后一句:

1
return boost::shared_ptr< T >( pt, pt2 );

前面虽然在控制块里构造出了管理对象,但是 shared_ptr 以及内部的 sp_counted_impl_pd 指向管理对象的指针都是空的,所以在返回之前,要做一次“矫正”;也就是这里重新构建一个 shared_ptr 的对象。

1
2
3
4
5
6
7
8
9
10
11
template< class Y >
shared_ptr( shared_ptr<Y> const & r, element_type * p ) BOOST_SP_NOEXCEPT : px( p ), pn( r.pn )
{}

shared_count(shared_count const & r): pi_(r.pi_) // nothrow
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
if( pi_ != 0 ) pi_->add_ref_copy();
}

有一个点需要注意,前面 sp_counted_impl_pd::ptr 这里一直是空的,并没有设置,因为这种 case 下,这个成员有点冗余。

注:这里可以对比 MSVC 和 libstdc++ 的实现。

接下来看一下直接从一个已创建的对象构造的流程,这块基本就是一气呵成了:

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
template<class Y>
explicit shared_ptr( Y * p ): px( p ), pn() // Y must be complete
{
boost::detail::sp_pointer_construct( this, p, pn );
}

template< class T, class Y >
inline void sp_pointer_construct( boost::shared_ptr< T > * ppx, Y * p, boost::detail::shared_count & pn )
{
boost::detail::shared_count( p ).swap( pn );
boost::detail::sp_enable_shared_from_this( ppx, p, p );
}

template<class Y> explicit shared_count( Y * p ): pi_( 0 )
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
try
{
pi_ = new sp_counted_impl_p<Y>( p );
}
catch(...)
{
boost::checked_delete( p );
throw;
}
};

template<class X> class sp_counted_impl_p: public sp_counted_base
{
private:

X * px_;

sp_counted_impl_p( sp_counted_impl_p const & );
sp_counted_impl_p & operator= ( sp_counted_impl_p const & );

typedef sp_counted_impl_p<X> this_type;

public:

explicit sp_counted_impl_p( X * px ): px_( px )
{
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
boost::sp_scalar_constructor_hook( px, sizeof(X), this );
#endif
}
};

可以看出这个场合下用的类是 sp_counted_impl_p,内部只包含了一个 X* px_

Conclusion

使用 make_shared() 在这里仍然保证引用计数控制块和托管对象使用同一块内存。

Boost 的实现也引入了 shared_count 这一中间层,但是在内存分配上没怎么使用 allocator。

Why Virtual Dtor is Not Necessary When Deleting From a Base Pointer

这点只需要回顾一下上面直接通过 ptr 构造 shared_ptr 的代码就可以发现,从构造函数开始,就是通过一个单独的 template <class Y> 将指针的具体类型逐层传递,一直到 sp_counted_impl_p 中。

How Custom Deleter Works and Why It Is Not Part of the Type

从构造函数开始跟

1
2
3
4
template<class Y, class D> shared_ptr( Y * p, D d ): px( p ), pn( p, d )
{
boost::detail::sp_deleter_construct( this, p );
}

因为 custom deleter 和 sp_deleter_construct() 无关,所以这里直接看 pn 的构造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class P, class D> shared_count( P p, D d ): pi_(0)
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
try
{
pi_ = new sp_counted_impl_pd<P, D>(p, d);
}
catch(...)
{
d(p); // delete p
throw;
}
}

sp_conted_impl_pd 这个类我们最开始分析 make_shared() 的时候已经遇到了。

这里看出,custom deleter 的类型和实际的值都是在构造的时候一层一层传递,直到底层的 ref-counted 块存储。

How enable_shared_from_this works

回顾一下使用 enable_shared_from_this 的两个条件:

  1. enable_shared_from_this 继承
  2. 实例用 shared_ptr 托管

首先看一下这个类的成员结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<class T> class enable_shared_from_this
{
protected:

BOOST_CONSTEXPR enable_shared_from_this() BOOST_SP_NOEXCEPT
{

~enable_shared_from_this() BOOST_SP_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
{}

private:
mutable weak_ptr<T> weak_this_;
};

类似的做法,内部包含了一个 weak_ptr;简单看一下 weak_ptr 的结构:

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
template<class T> class enable_shared_from_this
{
protected:

BOOST_CONSTEXPR enable_shared_from_this() BOOST_SP_NOEXCEPT
{

~enable_shared_from_this() BOOST_SP_NOEXCEPT // ~weak_ptr<T> newer throws, so this call also must not throw
{}

private:
mutable weak_ptr<T> weak_this_;
};

template<class T> class weak_ptr
{
private:

// Borland 5.5.1 specific workarounds
typedef weak_ptr<T> this_type;

public:

typedef typename boost::detail::sp_element< T >::type element_type;

BOOST_CONSTEXPR weak_ptr() BOOST_SP_NOEXCEPT : px(0), pn()
{
}

private:

template<class Y> friend class weak_ptr;
template<class Y> friend class shared_ptr;

element_type * px; // contained pointer
boost::detail::weak_count pn; // reference counter

}; // weak_ptr


class weak_count
{
private:
// <-- same as which in shared_count
sp_counted_base * pi_;

#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
int id_;
#endif

friend class shared_count;
};

和 MSVC 以及 libstdc++ 都不同,Boost 的 weak_ptr 并没有任何基类,不过和 libstdc++ 类似,weak_ptr 内部保存一个专门的 weak_count,其内部的计数控制块倒是和 shared_ptr 是复用的。

翻完了前面这么多介绍性的内容之后,我们挑 make_shared() 的用法作为具体例子分析。

回顾一下前面频繁提到的:

1
boost::detail::sp_enable_shared_from_this( &pt, pt2, pt2 );

因为前面我们知道被托管的类会从 enable_shared_from_this 继承,因此 pt2 是可以 cast 到 enable_shared_from_this<T>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template< class X, class Y, class T > inline void sp_enable_shared_from_this( boost::shared_ptr<X> const * ppx, Y const * py, boost::enable_shared_from_this< T > const * pe )
{
if( pe != 0 )
{
pe->_internal_accept_owner( ppx, const_cast< Y* >( py ) );
}
}

// Note: invoked automatically by shared_ptr; do not call
template<class X, class Y> void _internal_accept_owner( shared_ptr<X> const * ppx, Y * py ) const BOOST_SP_NOEXCEPT
{
if( weak_this_.expired() )
{
weak_this_ = shared_ptr<T>( *ppx, py );
}
}

这部分逻辑和 MSVC 的实现反而有点像。

经过这部分初始化之后,基类 enable_shared_from_this 内部的 weak_ptr 已经指向了正确的数据部分。

接下来看一下 shared_from_this() 的实现:

1
2
3
4
5
6
shared_ptr<T> shared_from_this()
{
shared_ptr<T> p( weak_this_ );
BOOST_ASSERT( p.get() == this );
return p;
}

一样的核心逻辑,从内部保存的 weak_ptr 中创建 shared_ptr

How Reference Counting Works

引用计数的相关操作由 shared_count 内部使用的 sp_counted_base 决定。

在比较新的环境下,boost 直接使用标准库的 atomic 设施作为引用计数

1
2
3
4
5
6
7
8
9
10
class sp_counted_base
{
private:

sp_counted_base( sp_counted_base const & );
sp_counted_base & operator= ( sp_counted_base const & );

std::atomic_int_least32_t use_count_; // #shared
std::atomic_int_least32_t weak_count_; // #weak + (#shared != 0)
};

而引用计数的相关操作也封装了一层薄薄的 utils wrapper

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
inline void atomic_increment( std::atomic_int_least32_t * pw )
{
pw->fetch_add( 1, std::memory_order_relaxed );
}

inline std::int_least32_t atomic_decrement( std::atomic_int_least32_t * pw )
{
return pw->fetch_sub( 1, std::memory_order_acq_rel );
}

inline std::int_least32_t atomic_conditional_increment( std::atomic_int_least32_t * pw )
{
// long r = *pw;
// if( r != 0 ) ++*pw;
// return r;

std::int_least32_t r = pw->load( std::memory_order_relaxed );

for( ;; )
{
if( r == 0 )
{
return r;
}

if( pw->compare_exchange_weak( r, r + 1, std::memory_order_relaxed, std::memory_order_relaxed ) )
{
return r;
}
}
}

这里几个操作选用的 memory ordering 有点意思,个人不是很明确这么选区的意思,但是猜想可能是如下原因:

  • increment 相关的操作只需要保证值的原子性即可,因为没有新值的要求
  • decrement 需要获取操作前的值(via fetch_sub()),判断是否应该开始进行清理了,所以需要保证这个点之前的操作对后续的可见性
  • conditional increment 同理

一个佐证是,usage_count() 会使用 acquire ordering,因为他确实要保证这个值的一致性。

1
2
3
4
long use_count() const // nothrow
{
return use_count_.load( std::memory_order_acquire );
}

另外类似的,use-count 递减至0后,仅会引起对象的析构,仅当 weak-count 为0时,整个控制块内存才会被释放。

注:如果打开 boost 的目录,会发现 boost 针对不同的环境和平台,定制了一系列的 sp_counted_base 实现,以尽可能提供最适合的计数控制。

How weak_ptr relates with shared_ptr

追踪一下 weak_ptr::lock() 的实现

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
shared_ptr<T> lock() const BOOST_SP_NOEXCEPT
{
return shared_ptr<T>( *this, boost::detail::sp_nothrow_tag() );
}

template<class Y>
shared_ptr( weak_ptr<Y> const & r, boost::detail::sp_nothrow_tag )
BOOST_SP_NOEXCEPT : px( 0 ), pn( r.pn, boost::detail::sp_nothrow_tag() )
{
if( !pn.empty() )
{
px = r.px;
}
}

inline shared_count::shared_count( weak_count const & r, sp_nothrow_tag ): pi_( r.pi_ )
#if defined(BOOST_SP_ENABLE_DEBUG_HOOKS)
, id_(shared_count_id)
#endif
{
if( pi_ != 0 && !pi_->add_ref_lock() )
{
pi_ = 0;
}
}

bool add_ref_lock() // true on success
{
return atomic_conditional_increment( &use_count_ ) != 0;
}

其中 atomic_condition_increment() 的实现在上一个话题分析过了。

Thread-safety of shared_ptr Instances

通过上面的分析,我们知道,哪怕是在 boost 里,一个 shared_ptr 也有 T* pxshared_count pn 两个成员。

因此多个线程同时读写一个 shared_ptr 是非线程安全。

又因为上面也可以看到,引用计数的增减是通过 std::atomic 达到,所以同时对多个同一引用的 shared_ptr 操作时线程安全的。

weak_ptrshared_ptr 的 promotion 也通过 CAS 保证了安全性。

Epilogue

Boost 版本的代码分析到这里就结束了。

这个版本其实分析的时候是蛋疼的,因为干扰代码太多(毕竟 boost 要兼容各种版本的编译器和各种神奇的平台),同时通过 vcpkg 安装 boost 的时候被 GFW 恶心的不行。

同时,因为这个版本和 libstdc++ 比较像,而且等于是第三遍分析,所以很多地方都分析的并不是那么细致…

Monthly Read Post in Apr 2018

What is Load Balancing?

Digital Ocean 出品的良心文章,覆盖了:

  • what is load balancing
  • Why load balancing
  • Traffic forwarding
  • How does load balancer choose backend servers
  • Avoid SPOF of load balancer itself

Introduction to modern network load balancing and proxying

非常详尽的一篇介绍 load balancing 的文章,前一篇的所有内容这篇都有,同时还做了一些深入的分析。

例如:L4/L7 的区别;基于 lb 的 service discovery .etc

尤其后面一大块讲网络拓扑的部分,没有实际经验的人表示看起来很痛苦。


CppCon 2015: Gabriel Dos Reis “Contracts for Dependable C++”

核心围绕着 interfaces as contract 来展开,针对三点:

  • precondition
  • invariants
  • postcondition

展开。

同时提到了一些(想法)构建 programming by contract 的基础设施,例如通过 attribute 来增强语义 blahblah

另,talk 有点无聊,看看 slider 就差不多了。


Dealing with randomness

文章前半部份批判了很多不专业的现象,openssl 再次躺枪。

后面提到了如果要做 cryptographic safe randomness,需要哪些东西。

最后批判了一下随机数去取模这种做法。

但是全文看半天也没有一个操作性很强的指导意见….


A good idea with bad usage: /dev/urandom

包含 dev/random 和 /dev/urandom 的区别,以及:

要从 /dev/urandom 中读取数据作为密码学安全的初始化种子应该如何正确的实现。


浅论Lock 与X86 Cache 一致性

Modern CAS implementation under the hood.

稍稍覆盖了一下 MESI/MESIF 协议。

但是总体来说感觉质量一般


CppCon 2015: Andrei Alexandrescu “Declarative Control Flow”

这个 Talk 质量很高,核心就是:如何利用基础的 ScopeGuard 构建出 ON_SCOPE_EXITON_SCOPE_FAILON_SCOPE_SUCCESS 语义,并自动执行相应代码。

RAII 这个特质在某个抽象角度上就是标题所说的 it is declarative

这几个东西基本已经作为了 facebook 基础库 Folly 的一部分。

最后提一下,使用 exception 作为 error reporting & handling 机制还是很苦难的,需要依赖很多语言设施(RAII在这里得天独厚),根据语言提供的特性不同会有不同的坑,同时对使用者要求很高。

目前看起来未来新语言使用异常作为核心错误处理机制的应该会越来越少。


Memory management in C and auto allocating sprintf() - asprintf()

文章分两部分:

第一部分叙述如何“正确”使用 mallocfree,核心包含

  • 需要处理 malloc 失败返回 NULL 的情况
  • 使用 free 释放内存之后应该把对应的 ptr 置 NULL,同时建议通过提供一个类似 insane_free 的宏来保证

实话说这部分观点差不多仅适合使用 C 的情况,并且需要自己处理内存分配失败在大多数应用系统上显得过于 overkiil。

C++ 在这点做的就比较好,直接抛出一个 bad_alloc 异常免得你有什么想法,不过我还是见过很多 catch bad_alloc 的代码。。。;chromium 的做法就比较激进了,在 Windows 上直接设置堆管理器,一旦内存分配失败就立马结束进程。

第二部分是阐述,如何实现一个跨平台的 asnprintf(),格式化字符串时不需要外部手动设置 buffer 大小。

这部分会涉及不少不同平台的 runtime 实现的差异性。


Programmer’s Toolbox Part 3: Consistent Hashing

Consistent Hashing

两篇非常棒的讲解 consistent hashing 的文章,第一篇侧重概念和意义,第二篇侧重实践,并且还包含了一个简单的实现。


Improving load balancing with a new consistent-hashing algorithm

一种结合 consistent hashing 和 least-connection 的负载均衡算法。


How to read in a file in C++ 1 & 2

作者比较了几种从文件读取内容的方式(C file IO, C++ fstream, iterator .etc)以及对应的性能指标。

针对同一个编译器的结果排名可以参考;跨跨编译器甚至随着编译器的优化可能结果会有不同,如果需要压榨性能最好还是做一个具体的 benchmark


Message Only Windows

message-only window 可以用来接受各种 windows 事件消息,作为 UI 消息泵使用。

局限性:

  • Not visible
  • No z-order
  • Cannot be enumerated
  • Does not receive broadcast messages

CppCon 2015: Detlef Vollmann “Executors for C++ - A Long Story …”

Executor proposal for C++.

实话说这个 Talk 我感觉效果不好。主题不明确,听起来有点云里雾里莫名其妙;演讲者口音也是捉急…

std::function Must be Copyable

前几天在写一个 ThreadPool 的轮子的时候,碰到一个问题:无论我用什么办法,都不能将 std::packaged_task 传递到 std::function 中,编译器始终提示访问了 std::packaged_task 被删除的拷贝构造函数。

在这个点卡了许久,未果,翻了一下 cpp reference,突然发现一个细节:std::function 必须是支持复制的,顿时茅塞顿开。

std::function 包裹的函数对象必须足够轻量,使得 pass-by-value 的 context 下不会出现明显的大量开销,因此这个类型要求指语义是符合逻辑的。

std::packaged_task 不是值语义,仅支持移动语义,因此无法作为 std::function 的一部分。

最后的解决方案是引入一个 std::shared_ptr 作为中间层,通过 lambda 保存这个 std::shared_ptr

附注:本来打算开一个 post 来简单讲解一下如何从头实现一个简单可用的 thread-pool,但是因为懒癌发作,代码写完了也就不想动手写文字了。后面看看是不是有机会可以补上。

Use Lambda With PostTask

曾经为了能在 PostTask() 里使用 lambda,实现了一个自动将 non-capturing lambda decay 到对应函数指针,在利用 base::Bind() 的方案[1]

但是在使用过程中发现这个方案有几个弊端:

  1. 因为只能使用 non-capture lambda,因此如果需要上下文,只能通过参数传入,这样代码写起来会很蛋疼
  2. VS 2013 + Resharper 的环境下,使用 lambda_decay() 的代码经常会出现静态分析的错误提示(不清楚是 VS2013 还是 Resharper 的问题),导致每段代码下面都会出现红色的曲线,非常惹眼

这两天因为需要重构某个模块,又顺便思考了这个问题,后来发现之前一开始思路不太对。

没有必要做一个兼容 base::Bind() 的解决方案,因为需要的只是在 message-loop 的 PostTask 里使用,这样只要能够将 lambda 包裹到一个 base::Callback<R()> 里就可以了,至于是不是利用 base::Bind() 完成,并不重要!

结合之前做的 base::Callbackstd::function转换,很自然的想到一个最终解决方案。

代码其实非常简单:

1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename R>
auto InvokeLambda(const std::function<R()>& fn) -> R
{
return fn();
}

template<typename F>
auto BindLambda(F&& lambda) -> base::Callback<typename std::result_of<F()>::type()>
{
using return_type = typename std::result_of<F()>::type;

return base::Bind(InvokeLambda<return_type>, std::function<return_type()>(std::forward<F>(lambda)));
}

判断返回类型是因为 PostTaskAndReplyWithResult() 会要求第一个 closure 有指定的返回值,以便第二个 clsoure 使用。

使用的时候就

1
2
3
4
5
6
7
8
9
10
11
12
13
std::string s = "hello, world";
base::Closure closure = BindLambda([&s] {
std::cout << "value of s: " << s << std::endl;
});

closure.Run();

auto cb = BindLambda([] {
std::cout << "with int result\n";
return 1024;
});

std::cout << cb.Run();

为了使用方便,我直接往 chromium base 开了一个 ext,作为以后的扩展。

因为现在 lambda 可以直接捕捉了,所以重构之后的代码看起来更具可读性;当然前提是要小心被捕捉对象的生命周期,通常这个上下文都应该使用 capture-by-value。

利用 vcpkg 编译带汇编优化的 libx264

使用 vcpkg 编译 libx264 有一个很重要的原因:可以获得 PDB,而且构建流程被大大精简了。

但是这里有一个坑:vcpkg 上的 libx264 模块编译默认是开启了 --disable-asm,意味着构建之后的二进制不会使用 SIMD 指令集,所以性能上会有很大的问题。

研究了一番之后找到了开启汇编优化的方案。

  1. 从官网下载并安装 nasm-2.13

    vcpkg 上通过 acquire-program 提供的 nasm 只有 2.12;而编译 vcpkg 上提供的 libx264-152 需要 nasm-2.13

  2. 编辑 libx264 的 portfile.cmake 文件

    在 set bash 的后面加一行

    1
    set(ENV{PATH} "c:/Programs/NASM/;$ENV{PATH}")

    这里注意替换你的真实安装目录

    然后去掉构建参数中的 --disable-asm

如果还有地方出错,记得根据提示查日志!

开启汇编优化后生成的二进制会略大,大概 900 多 KB

最后赞扬一下微软出品的 vcpkg,真是解决了 Windows 下编译一众开源软件的痛点。

Monthly Read Posts in Mar 2018

Sorting, caching and concurrency

通过引入基于时间的 sliding window 来标记并剔除(大概率)由错误产生的数据;并且可以利用 SW 来 schedule 数据什么时候从内存持久化到磁盘。

Patience sort 是 merge-sort 的一种变体,适合对 slightly unordered data 进行排序,最坏情况下退化为 merge-sort。

实现 optimistic locking 来避免使用 reader-writer locks,进而防止写饥饿。

引入一个原子的 sequence-number,初始状态为0;并且

  • merge 操作开始时,sequence-number 自增
  • merge 操作结束时,sequence-number 同样自增
    于是利用 sequence-number 的奇偶性来区分某个操作是否结束

如果写操作开始但是未完成,则读操作进行退避&重试。

感觉作者这么做应该是结合了业务的上下文;这个 optimistic locking 的策略至少有这么几个前提:

  1. 写操作很重要,不能有明显的延迟
  2. 写操作不会太慢,至少不会导致读操作的延迟超过无法承受的范围
  3. 平台提供的 reader-writer locks 对 writer starvation 的防治策略做的不够好

An Introduction to Lock-Free Programming

非常赞的 Lock-free 入门文章。

覆盖了 concepts, CAS operations, memory ordering & memory models

注:作者 preshing 是个 concurrency programming 领域的大触


Gentle Introduction to Lockless Concurrency

同样也是一篇 lock-free 的入门文章,但是这这篇文章比起上一篇就差太多了,明显不是一个层次的。

这篇是基于 Java 来谈 lockless,涉及的点明显局限在了语言自身,没有 preshing 那种站在 bare-metal 角度的犀利。

注:原文找不到了,大概率被作者自己删了,因此只有 pocket 的备份


The actor model in 10 minutes

一篇非常赞的 actor model 的介绍文章。

直接开篇就把模型的本质是 actor,以及 actor 的核心(primitive unit for computation & message passing)讲明了。

然后延伸到 fault tolerance 和 distribution 上的优势。

内容比 7 Concurrency Models in 7 Weeks 要好多了,讽刺的是这本书还是这篇文章的 reference 之一。


CppCon 2015: Gor Nishanov “C++ Coroutines - a negative overhead abstraction”

总结起来就是:

  • 我们其实很早就开始应用 coroutines 了
  • Concurrency TS 里提供的 CPS 也不是万能药
  • Coroutine is good, and We want coroutines!

CppCon 2015: Artur Laksberg “Concurrency TS Editor’s Report”

Brief introduction to concurrency ts.

涵盖了三点:

  • better std::future (with continutation & async/await)
  • latch & barrier
  • atomic shared_ptr

CppCon 2015: Scott Schurr “constexpr: Introduction”
CppCon 2015: Scott Schurr “constexpr: Applications”

第一篇是 constexpr 入门介绍。

演讲者挺有意思。另外最后面利用 unresolved symbol 来 force as compile time error 有点意思

第二篇,重点来了,干货十足,compile-time containers 可以研究好一会儿。

这下基本能明白为什么这哥们要开两个 talk 了,因为一个 talk 这么多内容说不玩啊。


Please Don’t Rely on Memory Barriers for Synchronization!

文章实际上是一片批判文,但是被批判的文章被作者删除了(估计是被喷得太惨了),所以不能拿来做交叉对比。。

文章核心是在强调原则性的做法,但是因为主要是为了批评,所以没有太多的干活,总结一下就是:

  • 多看书,不要当民科,自己发明轮子
  • 如无必要,不要使用 memory barrier 这种过于底层且没有移植性的东西
  • 锁不慢,这篇文章很容易给人错的观念
  • 先写对,再写快(老生常谈)

Overview of syslog

(Unix/Linux)系统提供的日志存储仓,对应的 daemon 叫 syslogd,使用的 Unix Domain Socket 名是 /dev/log

程序可以使用 syslog.h 文件中的 syslog()vsyslog() 函数往 syslog 写消息,一条消息最重要的有两部分:

  • facility,表明谁发的消息
  • priority,消息的重要程度

C++ Metaprogramming - Fedor Pikus - CppCon 2015

C++ metaprogramming- a paradigm shift - Louis Dionne - CppCon 2015

两个和 C++ TMP 有关的 talk。

前者是 introduction 性质的,后者嘛。。。其实就是 Boost.Hana 的宣传广告


Java替代C语言的可能性

作者(当时)认为这不存在可能性,总结了三个原因

  1. Java 程序员的平局水平比起 C 程序员的平均水平还有明显差距,尤其对底层、结构体系的理解
  2. 内存占用过高,带来一系列的性能问题,导致目前(当时)且一段时间内只能做上层应用
  3. 思维方式的问题,Java 程序员被厚重的框架束缚思维

虽然这是一篇10年前的文章,但是不得不说作者眼光独到。

现在这三个问题依旧,而且目前 Java 在互联网公司的主战场主要是 Android 开发(受到 Kotlin 挑战)和服务端(中间件,上层 web 服务,受到 Go 冲击),确实还未侵入 C 的核心领域。

不过个人感觉,未来编程面对的抽象层次会越来越高。


垃圾收集机制(Garbage Collection)批判

作者(当时)吐槽主要来自于:繁忙时刻且内存没有明显压力时不会触发 GC,而当内存有明显压力感知的时候,一次 GC 会导致极大的开销。


USING NON-BLOCKING AND ASYNCHRONOUS I/O (CK10 PROBLEM) IN LINUX AND WINDOWS (WITH EPOOL, IOCP, LIBEVENT/LIBEV/LIBUV/BOOST.ASIO AND LIBRT/LIBAIO)

C10K 问题下的各种解决方案。

从基础的 reactor(epoll / kqueue)到 proactor (IOCP)

同时涉及当前流行的网络框架:libev, libeio .etc

适合技术选型或者了解各平台下实现高性能并发处理的方式


Reactor vs. Proactor: Part 1 – The Reactor

PART 1 简单给予 epoll 做了几个例子。

例子代码写得不好,甚至还出现了利用异常做控制流…

还好作者太监了没有后续。


Designing C++ functions to write/save to any storage mechanism

这篇文章写于 2011 年,这个背景很重要。

在进入 C++ 11 的时代前,相当一部分 C++ 程序员还是只适应通过 OO 去构造抽象;而本身就是设计反面教科书的 ostream 体系让相当一部分人在错误的道路上越走越远。

作者吐槽的就是这点。

后文给出的 solution 说穿了其实就是,利用 template 进行依赖注入。

所有写操作,无论是写文件还是写 DB 还是写网络,核心都基于一个具体的 function object,而这个 function object 可以通过 template parameter 进行依赖解耦。

C++ 11 引入的 std::bind()、 lambda、甚至 std::function 都更加明确了条路子。

这篇文章有点类似孟岩曾经写的利用 bind + function 去避免继承(运行时多态)的滥用。


POSIX Threads Programming

Pthreads 简明教程。

覆盖

  • threads VS process (Why using threads)
  • pthreads 介绍
  • pthreads 创建/销毁
  • mutex
  • condition variable

可以作为入门级读物。


Copying code != Copying implementation

一开始我还以为是作者专门写文章解释他为什么会 copy code,看完才发现不是这样。

作者的观点是:(遵守 license)从开源项目里抄代码直接用是不靠谱的,一定要注意,代码所处的上下文,搞清楚是不是有什么 prerequisites 或者 hidden requirements。

中间还专门举了几个例子。


Visualising and Prioritizing Technical Debt

核心:将技术债具象化,无论是采用四象限法还是技术债树。