Chrome Task類分析

來源:互聯網
上載者:User

在上一篇《Chrome執行緒模式》之後,我們來實際看一看代碼。多線程編程完全基於訊息傳遞會比較麻煩,因為訊息的封裝和解析是比較麻煩的。不僅如此,被多個線程調用的其實是同一個對象的不同方法。比如class Work{ public:    void Start()    {         //CreateThread ... 建立線程或調用其它非同步函數,結束時會調用OnComplete方法             }private:    void OnCompleted();    // 會從另一個線程回調該方法。}對象Work會被不同的線程所訪問,如果內部沒有同步的話,則不是安全執行緒的。OOP封裝了資料和操作,但是沒有封裝執行線程--即可能被不同的線程同時訪問! 對以上代碼,我們當然可以為Work設定一個訊息接收器,然後從外部向Work對象發送ON_COMPLETE訊息,再由訊息接收器分發並調用OnComplete,從而保證Work對象是單線程訪問的。 在帶UI的程式裡,我們一般就是這樣做的,通過向主視窗PostMessage,然後在訊息響應函數裡寫處理代碼。 即便不帶UI,我們也可以建立一個隱藏(或者Message Only)的視窗,以把從其它線程來的回調都轉到主線程來執行。比如另一個線程...PostMessage(ON_COMPLETE)...主線程void OnMessage(msg){  switch(msg)    {        case ON_COMPLETE:            OnComplete();        ......    }}但是如果每個類都要建立視窗,實現訊息響應函數,還有參數的傳遞 ... 那就太繁瑣了。 這是因為,訊息傳遞的只能是資料,需要接收方在接收到資料後進行解析並調用相關的處理函數,這就需要接收方知道什麼訊息對應什麼處理函數,也就是說必須有大量的case ON_xxx。 但是,如果我們就是想讓一段代碼在指定線程上執行該怎麼辦? 特別的,從一個線程裡,指定另一個線程執行某指定的代碼,也就是說我們想傳遞的是可執行代碼而不是資料。其實也沒有那麼複雜,其實我們只要傳遞一個對象指標,然後接收方執行該對象的方法即可! 當然這要求必須是同一個進程內的,對象指標才有效,這在大多數情形下是不成問題的。 以下就是Chrome的實現方式,使用Task對象。要把代碼轉到指定線程上執行,要先把代碼用Task對象進行封裝! Task只有一個對外方法Run,沒有參數,所有資訊都在建立Task封裝進了Task對象內部,接收線程只要執行Task.Run即可。class Task{  // Tasks are automatically deleted after Run is called.  virtual void Run() = 0;};(為了突出重點,我們對代碼進行刪減,後面就不再一一說明)有了Task之後,我們就可以如下讓主線程(先用main_thread_loop代表)執行workObj的OnComplete方法。Work* workObj; ...main_thread_loop->PostTask(NewRunnableMethod(workObj, &Work::OnComplete));  下面我們看看Chrome是如何具體實現的。先看一個典型的用法 class MyClass {  private:   ScopedRunnableMethodFactory<MyClass> some_method_factory_;  public:   MyClass() : some_method_factory_(this) { }   void SomeMethod() {     some_method_factory_.RevokeAll();     ...   }   void ScheduleSomeMethod() {     // The factories are not thread safe, so always invoke on     // |MessageLoop::current()|.     MessageLoop::current()->PostDelayedTask(FROM_HERE,         some_method_factory_.NewRunnableMethod(&MyClass::SomeMethod),         kSomeMethodDelayMS);   } };我們可以看到MyClass有一個ScopedRunnableMethodFactory類型的成員變數some_method_factory_對象,用於建立指向MyClass方法的Task(RunnableMethod對象)。在ScheduleSomeMethod方法裡,我們建立了一個Task用於運行SomeMethod方法,該方法會在當前線程的稍後時間中執行。 一旦MyClass對象被刪除,那麼some_method_factory_在析構時會首先取消所有還沒有執行的Task,避免在MyClass刪除後還被另外的線程訪問而導致崩潰。 當然也可以隨時取消Task,即如上所示調用ScopedRunnableMethodFactory.RovokeAll方法。然後再來看實現代碼template<class T>class ScopedRunnableMethodFactory { public:  explicit ScopedRunnableMethodFactory(T* object) : weak_factory_(object) {  }  template <class Method>  inline Task* NewRunnableMethod(Method method) {    return new RunnableMethod<Method, Tuple0>(        weak_factory_.GetWeakPtr(), method, MakeTuple());  } protected:  template <class Method, class Params>  class RunnableMethod : public Task {   public:    RunnableMethod(const base::WeakPtr<T>& obj, Method meth, const Params& params)        : obj_(obj),          meth_(meth),          params_(params) {    }    virtual void Run() {      if (obj_)        DispatchToMethod(obj_.get(), meth_, params_);    }   private:    base::WeakPtr<T> obj_;    Method meth_;    Params params_;  }; private:  base::WeakPtrFactory<T> weak_factory_;};ScopedRunnableMethodFactory構造時需要傳入被調用對象的指標,並在NewRunnableMethod時傳入要調用方法的指標以及參數。這裡大量的使用了C++的模板技術,非此不可表示。  我們看到,一個Task就是一個RunnableMethod對象,其內部儲存了被調用對象的指標,方法地址(位移),以及參數(一般用Tuple表示),並最後調用模板方法DispatchToMethod。 暫且不管WeakPtrFactory和WeakPtr,後面再研究。template <class ObjT, class Method>inline void DispatchToMethod(ObjT* obj,                             Method method,                             const Tuple0& arg, Tuple0*) {  (obj->*method)();}為了使用的方便,還大量的使用了模板方法和重載,比如  template <class Method, class A>  inline Task* NewRunnableMethod(Method method, const A& a) {    return new RunnableMethod<Method, Tuple1<A> >(        weak_factory_.GetWeakPtr(), method, MakeTuple(a));  }用於建立指向帶一個參數的方法的Task,注意使用Tuple1來封裝參數。然後,調用如下的參數特例化函數進行展開。template <class Function, class A>inline void DispatchToFunction(Function function, const Tuple1<A>& arg) {  (*function)(arg.a);}因為使用了模板方法和重載,以及實用Tuple來封裝多個參數,才使得ScopedRunnableMethodFactory::Run方法只有一份即可。    virtual void Run() {      if (obj_)        DispatchToMethod(obj_.get(), meth_, params_);    }關於Tuple,我以為就是匿名的結構體,用以把多個參數合成一個結構體,以保證函數原型的一致。struct Tuple0 {};template <class A>struct Tuple1 {  Tuple1() {}  explicit Tuple1(typename TupleTraits<A>::ParamType a) : a(a) {}  A a;};template <class A, class B>struct Tuple2 { public:  Tuple2() {}  Tuple2(typename TupleTraits<A>::ParamType a,         typename TupleTraits<B>::ParamType b)      : a(a), b(b) {  }  A a;  B b;};到此為止,我們看到通過一系列的C++技巧,終於成就了Task* NewRunnableMethod(Method method) 的簡潔。現在來看前面擱置的問題WeakPtrFactory和WeakPtr。在前面的討論中,我們是通過ScopedRunnableMethodFactory::NewRunnableMethod方法來建立一個Task並傳遞給另外一個線程的,在這個Task裡儲存有被調用對象的指標,這樣才能執行它的方法。現在的問題是,如果被調用對象在Task還沒被執行之前就被刪除了,那可怎麼辦?C++程式崩潰,大多就是由此產生的,這是一個古老的問題。對此,我們當然也有不同的解決辦法。第一個,等待所有Task執行完畢被調用對象才能退出,帶來的問題是萬一Task永遠都執行不完或者要花很長的時間呢?第二個,被調用對象使用引用計數來控制生命週期,這樣只要Task還在,被調用對象就不可能刪掉。這樣的問題是,被調用對象萬一不帶引用計數呢?還有,Task不結束被調用對象就不能釋放,那很多資源就不能及時釋放了!第三個,使用弱引用(Weak Reference),這樣Task持有的是被調用對象的弱引用,只要被調用對象還在,弱引用就有效。如果被調用對象被刪除了,那麼弱引用就失效了,這可以檢測到,所以不會導致程式崩潰。Chrome裡使用的就是第三種方式。我們看到RunnableMethod對象裡包含了WeakPtr<T>對象obj_,並在Run時檢查obj_的有效性。    virtual void Run() {      if (obj_)        DispatchToMethod(obj_.get(), meth_, params_);    }顯然,WeakPtr並不是直接指向被調用對象,是什麼呢?template <typename T>class WeakPtr : public internal::WeakPtrBase { public:  WeakPtr() : ptr_(NULL) {  }  template <typename U>  WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other), ptr_(other.get()) {  }  T* get() const { return ref_.is_valid() ? ptr_ : NULL; }  operator T*() const { return get(); }  T* operator->() const {    return get();  }  void reset() {    ref_ = internal::WeakReference();    ptr_ = NULL;  }    // This pointer is only valid when ref_.is_valid() is true.  Otherwise, its  // value is undefined (as opposed to NULL).  T* ptr_;};class WeakPtrBase { public:  WeakPtrBase() {  } protected:  WeakPtrBase(const WeakReference& ref) : ref_(ref) {  }  WeakReference ref_;};由此可見,被調用對象指標儲存在ptr_變數裡,但是它的有效性依賴於ref_變數指向的對象WeakReference。class WeakReference { public:  class Flag : public RefCounted<Flag>, public NonThreadSafe {   public:    Flag(Flag** handle) : handle_(handle) {    }    void AddRef() {      RefCounted<Flag>::AddRef();    }    void Release() {      RefCounted<Flag>::Release();    }    void Invalidate() { handle_ = NULL; }    bool is_valid() const { return handle_ != NULL; }   private:    Flag** handle_;  };  WeakReference() {}  WeakReference(Flag* flag) : flag_(flag) {}  bool is_valid() const { return flag_ && flag_->is_valid(); } private:  scoped_refptr<Flag> flag_;};原來WeakReference內部持有一個Flag對象的引用,並且該對象是帶引用計數的。而Flag對象儲存了被引用對象是否有效標誌handle_(其類型其實無啥意義,只要空和非空兩種狀態即可。可能是為了調試方便,目前使用了Flag**),並可以設定其有效和無效。 我們現在可以猜測,Flag是由WeakPtrFactory建立的,並在退出時設定為無效的。template <class T>class WeakPtrFactory { public:  explicit WeakPtrFactory(T* ptr) : ptr_(ptr) {  }  WeakPtr<T> GetWeakPtr() {    return WeakPtr<T>(weak_reference_owner_.GetRef(), ptr_);  }  // Call this method to invalidate all existing weak pointers.  void InvalidateWeakPtrs() {    weak_reference_owner_.Invalidate();  }  // Call this method to determine if any weak pointers exist.  bool HasWeakPtrs() const {    return weak_reference_owner_.HasRefs();  } private:  internal::WeakReferenceOwner weak_reference_owner_;  T* ptr_;  DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);};WeakPtrFactory構造時儲存了被引用對象指標,並提供了GetWeakPtr()方法以獲得弱引用對象WeakPtr。而WeakPtr構造時從WeakReferenceOwner.GetRef()獲得了一個WeakReference對象。class WeakReferenceOwner { public:  WeakReferenceOwner() : flag_(NULL) {  }  ~WeakReferenceOwner() {    Invalidate();  }  WeakReference GetRef() const {    if (!flag_)      flag_ = new WeakReference::Flag(&flag_);    return WeakReference(flag_);  }  bool HasRefs() const {    return flag_ != NULL;  }  void Invalidate() {    if (flag_) {      flag_->Invalidate();      flag_ = NULL;    }  } private:  mutable WeakReference::Flag* flag_;};WeakReferenceOwner 的GetRef()返回的WeakReference都指向了同一個WeakReference::Flag對象,並在析構把Flag標誌至為無效。現在,被引用對象包含ScopedRunnableMethodFactory對象成員,後者又包含了WeakPtrFactory對象成員,同樣後後者又包含WeakReferenceOwner,因此被引用對象析構的時候,所有Weak Reference都成無效!關係如下:被引用對象->ScopedRunnableMethodFactory->WeakPtrFactor->WeakReferenceOwner->WeakReference::Flag我們看到WeakReference和WeakPtr等等都是以值傳遞的,唯有WeakReference::Flag傳遞的是指標,並且WeakReference::Flag是共用的對象,所以使用了引用計數來控制生命週期。當指向WeakReference::Flag的最後一個WeakReference被刪除了,WeakReference::Flag才會被刪除。即便有弱引用WeakReference一直都沒有被釋放,也無所謂,原始的被調用對象還是可以被釋放的,浪費的只是WeakReference::Flag佔用的空間,這是微不足道的。原來使用弱引用可以避免很多指標無效而導致非法訪問的問題!


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.