`
optman
  • 浏览: 44632 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

Chrome Task类分析

阅读更多

在上一篇《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占用的空间,这是微不足道的。


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

 


 

分享到:
评论
1 楼 Steven.Tan 2010-02-03  
你好,谢谢你写的这篇分析文章!我想小小补充一下,Flag类的成员handle_使用Flag**类型是必须的。handle_除了表示Flag是否有效之外,还有一个目的,就是在Flag对象析构时将引用Flag的指针置为NULL。这个指针就是WeakReferenceOwner的那个成员。

有一个情况,当所有的WeakPtr都不再引用要管理的对象时,Flag就会被释放,此时如果不将Owner的Flag指针置空,那么它就成了一个野指针了。

相关推荐

    Chrome的Todo.txt - 简单的任务管理「Todo.txt for Chrome - simple task management」-crx插件

    一个全功能的任务管理器,陪伴你的Todo.txt文件和智能...没有Google Analytics(分析)跟踪10.将设置导出和导入文件-将不同的Chrome配置文件设置为相同的设置11. Beta版*的同伴Android应用程序-https://play.google ...

    Google Chrome 6.0.451.0 Dev 版(一个由Google公司开发的网页浏览器)

    例如,您可以试试 Google Mail Checker(仅提供英文版)之类的扩展程序。  网页操作  无论您什么时候访问特定类型的网页,此类扩展程序都会检测出来,并在地址栏中显示一个图标。点击该图标可对网页执行相应的操作...

    Task7-百度首页制作.rar

    9. 浏览器兼容性:确保页面在主流浏览器(如Chrome、Firefox、Safari、Edge)上的表现一致,需要测试和调整代码。 10. 开发工具与调试:学习使用开发者工具(如浏览器内置的开发者工具)进行页面元素检查、网络请求...

    实现类似Chrome浏览器加载进度条效果

    本项目提供了一个学习案例,通过分析和研究,开发者可以掌握这一技术。 首先,Chrome浏览器的加载进度条通常是由`UIProgressView`或自定义视图来实现的。`UIProgressView`是苹果提供的一个内置组件,可以显示一个...

    Task timer-crx插件

    "Task Timer-crx插件"是一款专为Chrome浏览器设计的扩展程序,主要功能是帮助用户进行任务管理和时间追踪。这款插件以英文为主要界面语言,适用于那些需要高效管理时间、跟踪工作进度的用户,比如项目经理、自由职业...

    Task Focuser-crx插件

    Task Focuser是一款专为提高工作效率而设计的Chrome浏览器扩展程序。它主要针对那些需要频繁在多个任务之间切换的用户,帮助他们更加专注地完成每一项任务,避免因任务之间的频繁跳转导致的注意力分散。这款插件的...

    myInvenio Task Mining-crx插件

    任务挖掘是通过集合前端活动的桌面上的用户交互数据发现,监视和分析。 需要此插件,以便使用Myinvenio任务挖掘客户组件(代理和工作流设计器)对Chrome上的动作。 此插件不会收集并发送任何信息,而无需运行...

    Task Timer-crx插件

    安装"Task Timer.crx"文件意味着你需要将这个插件添加到支持CRX格式的浏览器中,通常是Google Chrome或基于Chromium的其他浏览器。安装过程通常包括下载文件,然后在浏览器的扩展管理界面手动加载未打包的扩展。这样...

    Task

    5. **ES6及更高版本**:ECMAScript 6(简称ES6)引入了大量新特性,如类(class)、模块(import/export)、箭头函数(=&gt;)、模板字符串(```)等,提高了代码的可读性和可维护性。 6. **Promise**和**Async/Await**...

    Memory management(内存管理)

    1. **Chrome Task Manager**:这是一个强大的工具,可以帮助开发者监控浏览器的性能指标,包括内存使用情况。通过观察内存使用量随时间的变化趋势,可以初步判断是否存在内存泄漏。 2. **Memory Timeline**:在...

    Task Time Counter-crx插件

    1. **安装与启用**:首先,用户需要将"Task_Time_Counter.crx"文件下载到本地,然后在支持CRX格式的浏览器(如Chrome)中进行安装。通常,只需拖拽该文件到浏览器的扩展管理页面即可完成安装。 2. **启动计时器**:...

    unit-task2

    10. **调试工具**:熟悉浏览器内置的开发者工具,如Chrome DevTools,学习如何进行代码调试和性能分析。 在"unit-task2-master"这个文件夹中,很可能包含了完成这个单元任务所需的所有资源,如源代码文件、示例、...

    ChromeRemoteSharp-master.rar

    理解async/await关键字和Task类的概念是使用该项目的关键。 3. **WebSocket通信**:Chrome Remote Interface (ChromeRI)是通过WebSocket连接实现的,这是一种在客户端和服务器之间建立持久连接的协议,支持双向实时...

    task4-master

    【标题】"task4-master" 是一个项目或者任务的命名,通常在软件开发环境中表示这是一个...以上是围绕JavaScript的一些核心知识点,"task4-master"可能涉及其中的某些方面,具体实现和目标需要根据实际项目代码来分析。

    boot-cljs-devtools:启动任务以添加CLJS的Chrome DevTool增强功能

    4. **性能分析**:利用Chrome DevTools的性能面板,Boot-CLJS-DevTools可以帮助开发者分析应用程序的性能瓶颈,优化代码。 5. **日志输出**:Boot-CLJS-DevTools能够将ClojureScript的日志信息输出到Chrome的控制台...

    be20-task7:CSS恢复任务

    在本任务中,我们面临的是一个关于CSS恢复的工作,标题为"be20-task7:CSS恢复任务"。... 在进行CSS恢复任务时,我们...通过对`be20-task7-main`的分析和修复,我们可以逐步恢复页面的视觉呈现,使之达到原始设计的要求。

    Emertech-Task_3

    - JavaScript也可以用于服务器端开发,Node.js是一个基于Chrome V8引擎的JavaScript运行环境,提供了丰富的服务器端API和工具。 9. **Web API和Web组件**: - Web API允许JavaScript与浏览器功能(如WebSocket、...

    Mean.io-Exercise-Task-manager

    4. **Node.js**:基于Chrome V8引擎的JavaScript运行环境,让JavaScript可以用于服务器端编程,提供了异步I/O和事件驱动的模型,提高了性能。 **MEAN.io锻炼任务管理器项目** 在"Mean.io-Exercise-Task-manager"这...

    jquery 表单验证插件Validform插件制作一行代码搞定整站的jquery表单验证

    4. **兼容性**:广泛支持各种浏览器,包括IE6+、Firefox、Chrome等。 **二、安装与引入** 在项目中使用Validform,首先需要引入jQuery库和Validform的JS及CSS文件。在HTML文件中添加以下代码: ```html &lt;!DOCTYPE...

    marionette-task:一个带有木偶和骨干的简单任务应用

    Puppeteer是由Google Chrome团队开发的一个Node库,它提供了一组高级API来通过DevTools协议控制Chromium或Chrome浏览器。通过Puppeteer,开发者可以自动化执行各种浏览器操作,如导航、点击、填写表单、截屏等,这...

Global site tag (gtag.js) - Google Analytics