Flutter引擎源码解读-内存管理篇

摘要

本文主要是对 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引擎设计的 RefPtrWeakPtr 还是比较小巧的,而且两者之间耦合没有标准库的 shared_ptrweak_ptr 那么大。

WeakPtr 是对 RefPtr 的一种辅助,同时也是必不可少的,在不同线程间共享数据的时候,WeakPtr 才是更应该使用的方式。

WeakPtr 通过 WeakPtrFactory 的生命周期来管理实例的生命周期,一般我们会把 WeakPtrFactory 放置到实例的类下面,这就达到了实例自己销毁的时候 WeakPtrFactory 也会随之销毁,所有的弱指针自然无法再指向实例,实现方式跟标准款的 weak_ptr 是完全不一样的,不愧为一种好方案。

整个实现方案也是完美的利用了 C++ 的 RAII 技术。

std::atomic
std::atomic<T>::fetch_add
std::memory_order

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345