Flutter引擎原始碼解讀-記憶體管理篇

稻子_Aadan發表於2020-05-07

摘要

本文主要是對 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

先來看 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

再看看 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

再看看 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::fetch_add

std::memory_order

相關文章