Chrome Task类分析

多线程编程完全基于消息传递会比较麻烦,因为消息的封装和解析是比较麻烦的。不仅如此,被多个线程调用的其实是同一个对象的不同方法。比如

Cpp代码   收藏代码
  1. class Work  
  2. {  
  3.  public:  
  4.     void Start()  
  5.     {  
  6.          //CreateThread ... 创建线程或调用其它异步函数,结束时会调用OnComplete方法  
  7.           
  8.      }  
  9. private:  
  10.     void OnCompleted();    // 会从另一个线程回调该方法。  
  11. }  
 

 


对象Work会被不同的线程所访问,如果内部没有同步的话,则不是线程安全的。OOP封装了数据和操作,但是没有封装执行线程--即可能被不同的线程同时访问! 

对以上代码,我们当然可以为Work设置一个消息接收器,然后从外部向Work对象发送ON_COMPLETE消息,再由消息接收器分发并调用OnComplete,从而保证Work对象是单线程访问的。 在带UI的程序里,我们一般就是这样做的,通过向主窗口PostMessage,然后在消息响应函数里写处理代码。 即便不带UI,我们也可以创建一个隐藏(或者Message Only)的窗口,以把从其它线程来的回调都转到主线程来执行。比如

Cpp代码   收藏代码
  1. 另一个线程  
  2. ...  
  3. PostMessage(ON_COMPLETE)  
  4. ...  
  5.   
  6.   
  7. 主线程  
  8.   
  9.   
  10. void OnMessage(msg)  
  11. {  
  12.   switch(msg)  
  13.     {  
  14.         case ON_COMPLETE:  
  15.   
  16.             OnComplete();  
  17.         ......  
  18.     }  
  19. }  
 

 


但是如果每个类都要创建窗口,实现消息响应函数,还有参数的传递 ... 那就太繁琐了。 这是因为,消息传递的只能是数据,需要接收方在接收到数据后进行解析并调用相关的处理函数,这就需要接收方知道什么消息对应什么处理函数,也就是说必须有大量的case ON_xxx。 但是,如果我们就是想让一段代码在指定线程上执行该怎么办? 特别的,从一个线程里,指定另一个线程执行某指定的代码,也就是说我们想传递的是可执行代码而不是数据。

其实也没有那么复杂,其实我们只要传递一个对象指针,然后接收方执行该对象的方法即可! 当然这要求必须是同一个进程内的,对象指针才有效,这在大多数情形下是不成问题的。 以下就是Chrome的实现方式,使用Task对象。要把代码转到指定线程上执行,要先把代码用Task对象进行封装! Task只有一个对外方法Run,没有参数,所有信息都在创建Task封装进了Task对象内部,接收线程只要执行Task.Run即可。

Cpp代码   收藏代码
  1. class Task{  
  2.   // Tasks are automatically deleted after Run is called.  
  3.   virtual void Run() = 0;  
  4. };  
 

(为了突出重点,我们对代码进行删减,后面就不再一一说明)

有了Task之后,我们就可以如下让主线程(先用main_thread_loop代表)执行workObj的OnComplete方法。

Cpp代码   收藏代码
  1. Work* workObj;   
  2. ...  
  3. main_thread_loop->PostTask(NewRunnableMethod(workObj, &Work::OnComplete));    
 

下面我们看看Chrome是如何具体实现的。


先看一个典型的用法

 
Cpp代码   收藏代码
  1. class MyClass {  
  2.   private:  
  3.    ScopedRunnableMethodFactory<MyClass> some_method_factory_;  
  4.   
  5.   
  6.   public:  
  7.    MyClass() : some_method_factory_(this) { }  
  8.   
  9.   
  10.    void SomeMethod() {  
  11.      some_method_factory_.RevokeAll();  
  12.      ...  
  13.    }  
  14.   
  15.   
  16.    void ScheduleSomeMethod() {  
  17.      // The factories are not thread safe, so always invoke on  
  18.      // |MessageLoop::current()|.  
  19.      MessageLoop::current()->PostDelayedTask(FROM_HERE,  
  20.          some_method_factory_.NewRunnableMethod(&MyClass::SomeMethod),  
  21.          kSomeMethodDelayMS);  
  22.    }  
  23.  };  
 
我们可以看到MyClass有一个ScopedRunnableMethodFactory类型的成员变量some_method_factory_对象,用于创建指向MyClass方法的Task(RunnableMethod对象)。在ScheduleSomeMethod方法里,我们创建了一个Task用于运行SomeMethod方法,该方法会在当前线程的稍后时间中执行。 一旦MyClass对象被删除,那么some_method_factory_在析构时会首先取消所有还没有执行的Task,避免在MyClass删除后还被另外的线程访问而导致崩溃。 当然也可以随时取消Task,即如上所示调用ScopedRunnableMethodFactory.RovokeAll方法。

然后再来看实现代码

Cpp代码   收藏代码
  1. template<class T>  
  2. class ScopedRunnableMethodFactory {  
  3.  public:  
  4.   explicit ScopedRunnableMethodFactory(T* object) : weak_factory_(object) {  
  5.   }  
  6.   
  7.   
  8.   template <class Method>  
  9.   inline Task* NewRunnableMethod(Method method) {  
  10.     return new RunnableMethod<Method, Tuple0>(  
  11.         weak_factory_.GetWeakPtr(), method, MakeTuple());  
  12.   }  
  13.  protected:  
  14.   template <class Method, class Params>  
  15.   class RunnableMethod : public Task {  
  16.    public:  
  17.     RunnableMethod(const base::WeakPtr<T>& obj, Method meth, const Params& params)  
  18.         : obj_(obj),  
  19.           meth_(meth),  
  20.           params_(params) {  
  21.     }  
  22.   
  23.   
  24.     virtual void Run() {  
  25.       if (obj_)  
  26.         DispatchToMethod(obj_.get(), meth_, params_);  
  27.     }  
  28.   
  29.   
  30.    private:  
  31.     base::WeakPtr<T> obj_;  
  32.     Method meth_;  
  33.     Params params_;  
  34.   };  
  35.   
  36.   
  37.  private:  
  38.   base::WeakPtrFactory<T> weak_factory_;  
  39. };  
 
ScopedRunnableMethodFactory构造时需要传入被调用对象的指针,并在NewRunnableMethod时传入要调用方法的指针以及参数。这里大量的使用了C++的模板技术,非此不可表示。  我们看到,一个Task就是一个RunnableMethod对象,其内部保存了被调用对象的指针,方法地址(偏移),以及参数(一般用Tuple表示),并最后调用模板方法DispatchToMethod。 暂且不管WeakPtrFactory和WeakPtr,后面再研究。

Cpp代码   收藏代码
  1. template <class ObjT, class Method>  
  2. inline void DispatchToMethod(ObjT* obj,  
  3.                              Method method,  
  4.                              const Tuple0& arg, Tuple0*) {  
  5.   (obj->*method)();  
  6. }  
 
为了使用的方便,还大量的使用了模板方法和重载,比如

Cpp代码   收藏代码
  1. template <class Method, class A>  
  2. inline Task* NewRunnableMethod(Method method, const A& a) {  
  3.   return new RunnableMethod<Method, Tuple1<A> >(  
  4.       weak_factory_.GetWeakPtr(), method, MakeTuple(a));  
  5. }  
 
用于创建指向带一个参数的方法的Task,注意使用Tuple1来封装参数。然后,调用如下的参数特例化函数进行展开。

Cpp代码   收藏代码
  1. template <class Function, class A>  
  2. inline void DispatchToFunction(Function function, const Tuple1<A>& arg) {  
  3.   (*function)(arg.a);  
  4. }  
 
因为使用了模板方法和重载,以及实用Tuple来封装多个参数,才使得ScopedRunnableMethodFactory::Run方法只有一份即可。

Cpp代码   收藏代码
  1. virtual void Run() {  
  2.   if (obj_)  
  3.     DispatchToMethod(obj_.get(), meth_, params_);  
  4. }  
 

关于Tuple,我以为就是匿名的结构体,用以把多个参数合成一个结构体,以保证函数原型的一致。

Cpp代码   收藏代码
  1. struct Tuple0 {  
  2. };  
  3.   
  4.   
  5. template <class A>  
  6. struct Tuple1 {  
  7.   Tuple1() {}  
  8.   explicit Tuple1(typename TupleTraits<A>::ParamType a) : a(a) {}  
  9.   
  10.   
  11.   A a;  
  12. };  
  13.   
  14.   
  15. template <class A, class B>  
  16. struct Tuple2 {  
  17.  public:  
  18.   Tuple2() {}  
  19.   Tuple2(typename TupleTraits<A>::ParamType a,  
  20.          typename TupleTraits<B>::ParamType b)  
  21.       : a(a), b(b) {  
  22.   }  
  23.   
  24.   
  25.   A a;  
  26.   B b;  
  27. };  
 

到此为止,我们看到通过一系列的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_的有效性。

Cpp代码   收藏代码
  1. virtual void Run() {  
  2.   if (obj_)  
  3.     DispatchToMethod(obj_.get(), meth_, params_);  
  4. }  
 

显然,WeakPtr并不是直接指向被调用对象,是什么呢?

Cpp代码   收藏代码
  1. template <typename T>  
  2. class WeakPtr : public internal::WeakPtrBase {  
  3.  public:  
  4.   WeakPtr() : ptr_(NULL) {  
  5.   }  
  6.   
  7.   
  8.   template <typename U>  
  9.   WeakPtr(const WeakPtr<U>& other) : WeakPtrBase(other), ptr_(other.get()) {  
  10.   }  
  11.   
  12.   
  13.   T* get() const { return ref_.is_valid() ? ptr_ : NULL; }  
  14.   operator T*() const { return get(); }  
  15.   T* operator->() const {  
  16.     return get();  
  17.   }  
  18.   
  19.   
  20.   void reset() {  
  21.     ref_ = internal::WeakReference();  
  22.     ptr_ = NULL;  
  23.   }  
  24.     
  25.   // This pointer is only valid when ref_.is_valid() is true.  Otherwise, its  
  26.   // value is undefined (as opposed to NULL).  
  27.   T* ptr_;  
  28. };  
  29.   
  30.   
  31. class WeakPtrBase {  
  32.  public:  
  33.   WeakPtrBase() {  
  34.   }  
  35.   
  36.   
  37.  protected:  
  38.   WeakPtrBase(const WeakReference& ref) : ref_(ref) {  
  39.   }  
  40.   
  41.   
  42.   WeakReference ref_;  
  43. };  
 
由此可见,被调用对象指针保存在ptr_变量里,但是它的有效性依赖于ref_变量指向的对象WeakReference。

Cpp代码   收藏代码
  1. class WeakReference {  
  2.  public:  
  3.   class Flag : public RefCounted<Flag>, public NonThreadSafe {  
  4.    public:  
  5.     Flag(Flag** handle) : handle_(handle) {  
  6.     }  
  7.   
  8.   
  9.     void AddRef() {  
  10.       RefCounted<Flag>::AddRef();  
  11.     }  
  12.   
  13.   
  14.     void Release() {  
  15.       RefCounted<Flag>::Release();  
  16.     }  
  17.   
  18.   
  19.     void Invalidate() { handle_ = NULL; }  
  20.     bool is_valid() const { return handle_ != NULL; }  
  21.   
  22.   
  23.    private:  
  24.     Flag** handle_;  
  25.   };  
  26.   
  27.   
  28.   WeakReference() {}  
  29.   WeakReference(Flag* flag) : flag_(flag) {}  
  30.   
  31.   
  32.   bool is_valid() const { return flag_ && flag_->is_valid(); }  
  33.   
  34.   
  35.  private:  
  36.   scoped_refptr<Flag> flag_;  
  37. };  
 
原来WeakReference内部持有一个Flag对象的引用,并且该对象是带引用计数的。而Flag对象保存了被引用对象是否有效的标志handle_(其类型其实无啥意义,只要空和非空两种状态即可。可能是为了调试方便,目前使用了Flag**),并可以设置其有效和无效。 我们现在可以猜测,Flag是由WeakPtrFactory创建的,并在退出时设置为无效的。

Cpp代码   收藏代码
  1. template <class T>  
  2. class WeakPtrFactory {  
  3.  public:  
  4.   explicit WeakPtrFactory(T* ptr) : ptr_(ptr) {  
  5.   }  
  6.   
  7.   
  8.   WeakPtr<T> GetWeakPtr() {  
  9.     return WeakPtr<T>(weak_reference_owner_.GetRef(), ptr_);  
  10.   }  
  11.   
  12.   
  13.   // Call this method to invalidate all existing weak pointers.  
  14.   void InvalidateWeakPtrs() {  
  15.     weak_reference_owner_.Invalidate();  
  16.   }  
  17.   
  18.   
  19.   // Call this method to determine if any weak pointers exist.  
  20.   bool HasWeakPtrs() const {  
  21.     return weak_reference_owner_.HasRefs();  
  22.   }  
  23.   
  24.   
  25.  private:  
  26.   internal::WeakReferenceOwner weak_reference_owner_;  
  27.   T* ptr_;  
  28.   DISALLOW_IMPLICIT_CONSTRUCTORS(WeakPtrFactory);  
  29. };  
 
WeakPtrFactory构造时保存了被引用对象指针,并提供了GetWeakPtr()方法以获得弱引用对象WeakPtr。而WeakPtr构造时从WeakReferenceOwner.GetRef()获得了一个WeakReference对象。

Cpp代码   收藏代码
  1. class WeakReferenceOwner {  
  2.  public:  
  3.   WeakReferenceOwner() : flag_(NULL) {  
  4.   }  
  5.   
  6.   
  7.   ~WeakReferenceOwner() {  
  8.     Invalidate();  
  9.   }  
  10.   
  11.   
  12.   WeakReference GetRef() const {  
  13.     if (!flag_)  
  14.       flag_ = new WeakReference::Flag(&flag_);  
  15.     return WeakReference(flag_);  
  16.   }  
  17.   
  18.   
  19.   bool HasRefs() const {  
  20.     return flag_ != NULL;  
  21.   }  
  22.   
  23.   
  24.   void Invalidate() {  
  25.     if (flag_) {  
  26.       flag_->Invalidate();  
  27.       flag_ = NULL;  
  28.     }  
  29.   }  
  30.   
  31.   
  32.  private:  
  33.   mutable WeakReference::Flag* flag_;  
  34. };  
 
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占用的空间,这是微不足道的。


原来使用弱引用可以避免很多指针无效而导致非法访问的问题!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值