摘要
本文主要是对 Flutter 引擎中的内存管理相关的源码进行解读,Flutter 引擎核心代码大都是用 C++ 写的,内存管理主要是引用计数,结合C++语言本身的灵活性,以很少的代码实现了类似于Objective-C语言的ARC的内存管理能力。
开始之前
C++代码中一般会遇到很多宏,我们要理解这些宏的意义还是需要参考其背后的源码,在内存模型相关的源码中遇到的宏,开篇之前我们先做个简单的介绍,
[flutter/engine/fml/macros.h]
宏名字本身就是最好的注释,C++中通过 delete来禁用copy, assign, move等函数。
#define FML_DISALLOW_COPY(TypeName) TypeName(const TypeName&) = delete
#define FML_DISALLOW_ASSIGN(TypeName) \
TypeName& operator=(const TypeName&) = delete
#define FML_DISALLOW_MOVE(TypeName) \
TypeName(TypeName&&) = delete; \
TypeName& operator=(TypeName&&) = delete
#define FML_DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
TypeName& operator=(const TypeName&) = delete
#define FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName) \
TypeName(const TypeName&) = delete; \
TypeName(TypeName&&) = delete; \
TypeName& operator=(const TypeName&) = delete; \
TypeName& operator=(TypeName&&) = delete
#define FML_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
TypeName() = delete; \
FML_DISALLOW_COPY_ASSIGN_AND_MOVE(TypeName)
源码结构
- flutter/engine/fml/memory
- ref_ptr.h
- ref_ptr_internal.h
- ref_counted.h
- ref_counted_internal.h
- weak_ptr.h
- weak_ptr_internal.h
- weak_ptr_internal.cc
- thread_checker.h
关键概念
我们需要关注以下几个关键的概念:
- 引用指针,引用计数的实现就是通过引用指针进行的,内置引用计数;
- 弱指针,同样的我们需要有弱指针来表示对一个内存的引用不会增加引用计数;
- Thread Safe,在内存管理相关的层面上,我们需要关注指针是否是线程安全的
引用指针
引用指针可以指向继承了 RefCountedThreadSafe
的类的实例,并通过引用指针本身的栈内存来实现类实例的引用计数的增减。
RefCountedThreadSafeBase
源码路径,[fml/memory/ref_counted_internal.h]
本类将作为所有使用引用计数的类的最开始的基类存在,主要提供引用计数最基本的三个能力:
- ref_count_,在初始化时会默认为1u
- AddRef,增加引用计数,同时确保操作的原子性
- Release,减少引用计数,同时确保操作的原子性
class RefCountedThreadSafeBase {
public:
void AddRef() const {
#ifndef NDEBUG
FML_DCHECK(!adoption_required_);
FML_DCHECK(!destruction_started_);
#endif
ref_count_.fetch_add(1u, std::memory_order_relaxed);
}
bool HasOneRef() const {
return ref_count_.load(std::memory_order_acquire) == 1u;
}
void AssertHasOneRef() const { FML_DCHECK(HasOneRef()); }
protected:
RefCountedThreadSafeBase();
~RefCountedThreadSafeBase();
// Returns true if the object should self-delete.
bool Release() const {
#ifndef NDEBUG
FML_DCHECK(!adoption_required_);
FML_DCHECK(!destruction_started_);
#endif
FML_DCHECK(ref_count_.load(std::memory_order_acquire) != 0u);
if (ref_count_.fetch_sub(1u, std::memory_order_release) == 1u) {
std::atomic_thread_fence(std::memory_order_acquire);
#ifndef NDEBUG
destruction_started_ = true;
#endif
return true;
}
return false;
}
#ifndef NDEBUG
void Adopt() {
FML_DCHECK(adoption_required_);
adoption_required_ = false;
}
#endif
private:
mutable std::atomic_uint_fast32_t ref_count_;
#ifndef NDEBUG
mutable bool adoption_required_;
mutable bool destruction_started_;
#endif
FML_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafeBase);
};
inline RefCountedThreadSafeBase::RefCountedThreadSafeBase()
: ref_count_(1u)
#ifndef NDEBUG
,
adoption_required_(true),
destruction_started_(false)
#endif
{
}
inline RefCountedThreadSafeBase::~RefCountedThreadSafeBase() {
#ifndef NDEBUG
FML_DCHECK(!adoption_required_);
// Should only be destroyed as a result of |Release()|.
FML_DCHECK(destruction_started_);
#endif
}
从类名看,就表示其是线程安全的,从源码层面,实现确保了 ref_count_
的原子操作。
首先看下其定义:
mutable std::atomic_uint_fast32_t ref_count_;
atomic_uint_fast32_t 实际上是 std::atomic<uint_fast32_t>
,表示当前类型可以进行原子操作。
其次,看AddRef的实现,这里用到了 std::memory_order_relaxed
,仅保证此操作的原子性。
ref_count_.fetch_add(1u, std::memory_order_relaxed);
同样的,在Release函数中,也会使用到确保操作原子性的调用。
ref_count_.fetch_sub(1u, std::memory_order_release);
RefCountedThreadSafe
源码路径,[fml/memory/ref_counted.h]
RefCountedThreadSafe 将作为所有使用引用计数来管理内存的类的基类, 通过模板类型来引入实际的子类的类型。
template <typename T>
class RefCountedThreadSafe : public internal::RefCountedThreadSafeBase {
public:
void Release() const {
if (internal::RefCountedThreadSafeBase::Release())
delete static_cast<const T*>(this);
}
protected:
RefCountedThreadSafe() {}
~RefCountedThreadSafe() {}
private:
#ifndef NDEBUG
template <typename U>
friend RefPtr<U> AdoptRef(U*);
void Adopt() { internal::RefCountedThreadSafeBase::Adopt(); }
#endif
FML_DISALLOW_COPY_AND_ASSIGN(RefCountedThreadSafe);
};
可以看到,在 Release 函数中最终会 delete 掉类实例的内存,也就是一旦引用计数为0的时候会马上释放掉内存。
这里定义了一个友元函数,在 RefPtr中会看到起使用的场景。
template <typename U> friend RefPtr<U> AdoptRef(U*);
RefPtr
源码路径,[fml/memory/ref_ptr.h]
RefPtr
是引用指针的实现类,需要关注的是各类构造、赋值、析构函数的行为。构造函数、拷贝构造函数需要 AddRef
,转移构造函数不需要 AddRef
;析构函数需要 Release
;拷贝赋值函数AddRef
新的对象,Release
久的对象;转移赋值函数不需要变更引用计数。
- 构造函数、拷贝构造函数会调用
AddRef
函数
template <typename U>
explicit RefPtr(U* p) : ptr_(p) {
if (ptr_)
ptr_->AddRef();
}
RefPtr(const RefPtr<T>& r) : ptr_(r.ptr_) {
if (ptr_)
ptr_->AddRef();
}
- 转移构造函数不会调用
AddRef
RefPtr(RefPtr<T>&& r) : ptr_(r.ptr_) { r.ptr_ = nullptr; }
template <typename U>
RefPtr(RefPtr<U>&& r) : ptr_(r.ptr_) {
r.ptr_ = nullptr;
}
- 析构函数会调用
Release
~RefPtr() {
if (ptr_)
ptr_->Release();
}
- 拷贝赋值的实现,
AddRef
新的对象,Release
久的对象
RefPtr<T>& operator=(const RefPtr<T>& r) {
// Call |AddRef()| first so self-assignments work.
if (r.ptr_)
r.ptr_->AddRef();
T* old_ptr = ptr_;
ptr_ = r.ptr_;
if (old_ptr)
old_ptr->Release();
return *this;
}
template <typename U>
RefPtr<T>& operator=(const RefPtr<U>& r) {
// Call |AddRef()| first so self-assignments work.
if (r.ptr_)
r.ptr_->AddRef();
T* old_ptr = ptr_;
ptr_ = r.ptr_;
if (old_ptr)
old_ptr->Release();
return *this;
}
- 转移赋值的实现,不存在引用计数的变更
RefPtr<T>& operator=(RefPtr<T>&& r) {
RefPtr<T>(std::move(r)).swap(*this);
return *this;
}
template <typename U>
RefPtr<T>& operator=(RefPtr<U>&& r) {
RefPtr<T>(std::move(r)).swap(*this);
return *this;
}
MakeRefCounted
,这是提供给私有化构造函数的类创建对应指针的函数,这个函数会调用帮助类来实现
template <typename T, typename... Args>
RefPtr<T> MakeRefCounted(Args&&... args) {
return internal::MakeRefCountedHelper<T>::MakeRefCounted(
std::forward<Args>(args)...);
}
template <typename T>
class MakeRefCountedHelper final {
public:
template <typename... Args>
static RefPtr<T> MakeRefCounted(Args&&... args) {
return AdoptRef<T>(new T(std::forward<Args>(args)...));
}
};
然后实际的私有化构造函数的类需要将帮助类定义为友元类,一般通过如下的宏来完成
#define FML_FRIEND_MAKE_REF_COUNTED(T) \
friend class ::fml::internal::MakeRefCountedHelper<T>
弱指针
弱指针的设计比较有意思,增加了一个 WeakPtrFactory
来持有真正的指针,每次获取一个 WeakPtr
时,复制一份 WeakPtrFlag
来表示这个 WeakPtr
所指向指针的生命周期。
先来看 WeakPtrFlag
,继承自RefCountedThreadSafe<WeakPtrFlag>
,只是带了一个 is_valid_
的简单类型,标记是否有效。
class WeakPtrFlag : public fml::RefCountedThreadSafe<WeakPtrFlag> {
public:
WeakPtrFlag() : is_valid_(true) {}
~WeakPtrFlag() {
FML_DCHECK(!is_valid_);
}
bool is_valid() const { return is_valid_; }
void Invalidate() {
FML_DCHECK(is_valid_);
is_valid_ = false;
}
private:
bool is_valid_;
FML_DISALLOW_COPY_AND_ASSIGN(WeakPtrFlag);
};
再看看 WeakPtrFactory
,最关键的函数是 GetWeakPtr
,可以从 WeakPtrFactory
获取任意多个 WeakPtr
,但都不会增加实例的引用计数
template <typename T>
class WeakPtrFactory {
public:
explicit WeakPtrFactory(T* ptr)
: ptr_(ptr), flag_(fml::MakeRefCounted<fml::internal::WeakPtrFlag>()) {
FML_DCHECK(ptr_);
}
~WeakPtrFactory() {
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
flag_->Invalidate();
}
WeakPtr<T> GetWeakPtr() const {
return WeakPtr<T>(ptr_, flag_.Clone(), checker_);
}
private:
T* const ptr_;
fml::RefPtr<fml::internal::WeakPtrFlag> flag_;
DebugThreadChecker checker_;
FML_DISALLOW_COPY_AND_ASSIGN(WeakPtrFactory);
};
再看看 WeakPtr
本身的实现,主要关注以下几个函数就可以了,特别是要知道这端代码,*this
实际上会通过 explicit operator bool() const
进行转换。
explicit operator bool() const {
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
return flag_ && flag_->is_valid();
}
T* get() const {
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
return *this ? ptr_ : nullptr;
}
T& operator*() const {
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
FML_DCHECK(*this);
return *get();
}
T* operator->() const {
FML_DCHECK_CREATION_THREAD_IS_CURRENT(checker_.checker);
FML_DCHECK(*this);
return get();
}
线程安全
源码路径,[fml/memory/thread_checker.h]
ThreadChecker
非常简单,仅提供了判断是否是当前线程的一个函数,可以再源码上看到很多地方会使用到。
// Returns true if the current thread is the thread this object was created
// on and false otherwise.
bool IsCreationThreadCurrent() const {
return !!pthread_equal(pthread_self(), self_);
}
#if !defined(NDEBUG) && false
#define FML_DECLARE_THREAD_CHECKER(c) fml::ThreadChecker c
#define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) \
FML_DCHECK((c).IsCreationThreadCurrent())
#else
#define FML_DECLARE_THREAD_CHECKER(c)
#define FML_DCHECK_CREATION_THREAD_IS_CURRENT(c) ((void)0)
#endif
总结
Flutter引擎设计的 RefPtr
和 WeakPtr
还是比较小巧的,而且两者之间耦合没有标准库的 shared_ptr
和 weak_ptr
那么大。
WeakPtr
是对 RefPtr
的一种辅助,同时也是必不可少的,在不同线程间共享数据的时候,WeakPtr
才是更应该使用的方式。
WeakPtr
通过 WeakPtrFactory
的生命周期来管理实例的生命周期,一般我们会把 WeakPtrFactory
放置到实例的类下面,这就达到了实例自己销毁的时候 WeakPtrFactory
也会随之销毁,所有的弱指针自然无法再指向实例,实现方式跟标准款的 weak_ptr 是完全不一样的,不愧为一种好方案。
整个实现方案也是完美的利用了 C++ 的 RAII 技术。