在上一篇《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佔用的空間,這是微不足道的。原來使用弱引用可以避免很多指標無效而導致非法訪問的問題!