文档章节

Chrom 的线程模型

zhangyujsj
 zhangyujsj
发布于 2015/08/22 18:40
字数 3348
阅读 16
收藏 0
点赞 0
评论 0

线程(http://www.chromium.org/developers/design-documents/threading )

a) 概述

Chromium是一个超级多线程的产品,我们尝试让UI的反应尽可能的快,这样就意味着不要用任何的I/O操作或者长操作来阻塞UI 线程,我们的方法是在线程之间使用消息传递,我们不鼓励使用阻塞和线程安全的对象,取而代之的是,对象都只存在一个线程中,我们在线程间传递消息通讯,为大部分的跨线程请求使用callback接口返回结果。

Thread 对象定义在 base/thread.h 中,一般来说,你都可以使用这里已经有的线程,而非自己去重写一个新的。我们已经有太多的线程,以致我们很难去跟踪。每条线程都有一个 MessageLoop (base/message_loop.h)来处理这个线程的消息,你可以获取每个线程的MessageLoop,用这个函数就行 Thread.message_loop() 。

b) 已有的线程

大部分线程都被 BrowserProcess 对象管理,BrowserProcess对象就像是browser”进程的服务管理者一样,一般所有的事情都发生在UI线程上(也就是程序开始的主线程),我们将某些类型的处理压进这些线程里,以下这些线程都可以获取接收这些处理:

io_thread分发线程,处理 browser 进程和 子进程之间的通信,也是所有资源请求(webpage loads)调配的地方。

file_thread一个处理文件的线程, 当你想做一些阻塞的文件系统的操作时,派遣到这个线程。

db_thread数据库操作的线程,例如,cookie service 会在这个线程里做一些 sqlite 的操作。注意,history 不会使用这个线程。

safe_browser_thread

有几个组件拥有它们自己的线程:

historyhistory service 有自己的线程,其实它可以合并到 db_thread 中,不过,我们需要确定事情发生的精确顺序,例如,cookies load会先于 history load,因为cookies需要被先读,而且 history 的初始化比较长并且是阻塞的。

Proxy service :net/http/http_proxy_service.cc. 

Automation proxy :这个线程用来和 UI test 程序通信,驱动应用。

c) 让浏览器保持良好的响应性

在概述中可以看到,我们避免在UI线程中做任何的阻塞IO操作,以便让UI保持良好的响应性,另一个隐藏的意思是,我们也需要避免在IO 线程里做阻塞的IO 操作,原因是,如果我们在IO 线程中做耗时操作,例如磁盘读写,那么IPC 消息就不会被及时处理,会影响到用户和页面的交互,我们可以用 异步IO或者完成端口 来做这个事情。

另外一个需要注意的是,不要线程间彼此阻塞,锁只能用于多线程间共享的数据结构,如果一个线程执行比较耗时的操作或者磁盘操作的时候,就应该放开锁,只有得到结果的时候,才使用锁来交换新数据。这里是一个例子:PluginList::LoadPlugins (src/webkit/glue/plugins/plugin_list.cc) ,如果你非要用锁,这里是一些比较好的实践帮助你避免一些陷阱(http://www.chromium.org/developers/lock-and-condition-variable )。

为了写 non-blocking 的代码,chrome的很多 API 都是异步的,这也意味着,这些代码可能是运行在某个线程中,然后通过某些委托接口返回;或者他们会在请求操作完成时候会触发一个base::Callback<> 对象。执行操作会在下面的 PostTask 章节中说到。

d) 线程中的技术点

i. base::Callback<>, Async APIs, and Currying

base::Callback<>callback.h ) 是一个模版类,有一个Run函数,这个函数是函数指针的泛化,这个Callback对象是被 base::Bind 函数创建的,异步API经常会采用 base::Callback<> 作为一种手段来异步返回操作的结果,下面是一个例子:

void ReadToString(const std::string& filename, const base::Callback<void(const std::string&)>& on_read);

void DisplayString(const std::string& result) {

  LOG(INFO) << result;

}

void SomeFunc(const std::string& file) {

  ReadToString(file, base::Bind(&DisplayString));

};

在这个例子里,base::Bind 将 DisplayString 这个函数变成了 base::Callback<void(const std::string& result)> 对象,base::Callback<> 对象的类型决定于参数。为什么不直接传函数指针呢?这是因为 base::Bind 允许调用者去适配函数接口,并且通过 Curring(http://en.wikipedia.org/wiki/Currying ) 附上一些额外的内容,例如,我们已经有一个工具函数 DisplayStringWithPrefix 可以用参数作为前缀,我们使用 base::Bind 来适配这个接口如下:

void DisplayStringWithPrefix(const std::string& prefix, const std::string& result) {

  LOG(INFO) << prefix << result;

}

void AnotherFunc(const std::string& file) {

  ReadToString(file, base::Bind(&DisplayStringWithPrefix, "MyPrefix: "));

};

这可以用来代替创建一个适配函数,拥有一个前缀成员变量的小类。还要注意的是“MyPrefix参数实际上是一个const char *,而DisplayStringWithPrefix实际上想要一个const  std::string。像正常功能调度,base::Bind,将强制转换参数的类型,如果可能的话。请参阅 base:: bind()如何处理参数 会讲更多的细节,关于参数存储,复制和特殊处理的参考。

ii. PostTask

分配给另外线程最底层的函数是使用 MessageLoop.PostTask 和 MessageLoop.PostDelayTask( base/message_loop.h)

PostTask分配了一个task在指定的线程中运行,task 被定义为 base::Closure

base::Closure 是一个 typedef :

Typedef  base::Closure  base::Callback<void(void)>

PostDelayedTask 分配一个task给指定线程,这个task会延迟一段时间再执行,一个tasktypedef 为 base::Closure,这个定义实际是一个Callback对象,包含了一个Run函数,这个对象被 base::Bind()函数创建,处理一个taskmessage_loop最终会调用 base::Closure 的 Run 函数,然后会把task 对象的引用 drops

PostTask 和 PostDelayTask 都有一个tracked_objects::Location 的参数,这个参数是为了满足轻量级的调试用途(task计数、正等待的task以及已完成的task都可以通过Url aboutobjects被监测到,当然我们需要debug版本)。一般来说,FROM_HERE 宏是这个参数的适当的值。

注意,新task在message_loop里运行,我们指定的任何delay值,都会受制于操作系统的timer 精度,这就意味这,在windows下,非常小的timeout,例如10ms以下,将有可能无法实现(delay会更长)。使用0作为PostDelayedTask 的参数,就等价于直接调用 PostTask

PostTask也用来在当前线程中做一些事情,有时候在message_loop的当前处理返回之后。这样一个在当前线程的延续操作,将可以用来保证这个线程中的其他关键任务不会被“饿死”。

下面的例子是为一个函数创建一个task,并post它到别的线程(这个例子是 post到 file thread:

void WriteToFile(const std::string& filename, const std::string& data);

BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                        base::Bind(&WriteToFile, "foo.txt", "hello world!"));

你应该总是使用 BrowserThread 在线程间投递task,不要缓存MessageLoop的 指针,因为这会造成一些bug,例如这些指针已经被删除了,你却还在使用他们。更多的信息可以参考这里:http://www.chromium.org/developers/design-documents/threading/suble-threading-bugs-and-patterns-to-avoid-them 

iii. base::Bind() and class method

base::Bind API 也支持调用类方法,语法非常类似 base::Bind 一般的函数,除了第一个参数是类对象,默认情况下,PostTask 使用的对象应该是线程安全、使用引用计数的对象,引用计数保证对象在另一个线程中调用将继续存在,直到task完成。

class MyObject : public base::RefCountedThreadSafe<MyObject> {
 public:
  void DoSomething(const std::string16& name) {
    thread_->message_loop()->PostTask(
       FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, this, name));

  }

  void DoSomethingOnAnotherThread(const std::string16& name) {
    ...
  }

 private:
  // Always good form to make the destructor private so that only RefCountedThreadSafe can access it.
  // This avoids bugs with double deletes.
  friend class base::RefCountedThreadSafe<MyObject>;

  ~MyObject();

  Thread* thread_;
};

如果你有一个外部的同步结构,并且你可以完全肯定这个对象会在task等待执行的过程中一直存在,那么你可以在调用 base::Bind() 的时候使用 base::Unretained() 封装这个对象指针,这样可以关闭引用计数。这个也允许用在不用引用计数的类中,不过当你这样做的时候,要非常小心。

iv. base::Bind() 如何处理参数

参数传递给 base::Bind() 的时候会拷贝到一个内部的 InvokerStorage 结构对象base/bind_internal.h)。当这个函数执行完的时候,能看到参数的拷贝。如果你的目标函数或者方法使用一个 const 的引用,这就非常重要了。如果你需要一个原始参数的引用,你可以使用 base::ConstRef() 来包装这个参数。小心使用这个,因为如果你不能保证这个引用直到task执行完成之后都存在,那就会很危险了。尤其是一个变量在堆上创建的时候,使用 base::ConstRef() 是很不安全的,除非你能保证这个堆一直有效,直到完成这个异步task

有时候,你会想传一个引用计数的对象作为参数(记得使用 RefCountedThreadSafe 和不纯的RefCounted 作为基类),保证对象在整个请求的过程中都存在,base:Bind 生成的 Closure 应该保持一个引用,这可以在传递 scoped_refptr 作为参数类型或者使用 make_scoped_refptr 包装原始指针的时候来做这个事情。

class SomeParamObject : public base::RefCountedThreadSafe<SomeParamObject> {
 ...
};

class MyObject : public base::RefCountedThreadSafe<MyObject> {
 public:
  void DoSomething() {
    scoped_refptr<SomeParamObject> param(new SomeParamObject);
    thread_->message_loop()->PostTask(FROM_HERE
       base::Bind(&MyObject::DoSomethingOnAnotherThread, this, param));

  }

  void DoSomething2() {
    SomeParamObject* param = new SomeParamObject;
    thread_->message_loop()->PostTask(FROM_HERE
       base::Bind(&MyObject::DoSomethingOnAnotherThread, this, 

                         make_scoped_refptr(param)));

  }

  // Note how this takes a raw pointer. The important part is that

  // base::Bind() was passed a scoped_refptr; using a scoped_refptr

  // here would result in an extra AddRef()/Release() pair.

  void DoSomethingOnAnotherThread(SomeParamObject* param) {

...
  }
};

你如果想直接传对象而不是传它的引用,那你可以用 base::Unretained()再次提醒,使用这个需要非常的小心。

如果你的对象有一个实际的析构函数,需要运行在某个线程里,你可以使用下面的这个trait,这个是需要的,因为一个时间先后的问题会导致一个task在代码投递之前完成执行,这样不会破坏堆栈。

class MyObject : public base::RefCountedThreadSafe<MyObject, BrowserThread::DeleteOnIOThread> {

...};

v. base::WeakPtr 和 Cancellation

我们有时候会在我们的对象“之后”干一些事情,比如你用 PostDelayTask,当你的task被调用的时候,却发现你的对象已经被删除了,这种事情导致大量的Crash,尤其是在程序退出的时候。

在这种情况下,你可以用 base::WeakPtr 和 base::WeakPtrFactory ( base/memory/weak_ptr.h ) 来确定任何的调用都不能发生在对象的生命周期之外,这里没有使用引用计数。base::Bind 函数机制会特别清楚 base::WeakPtr 对象,如果这个对象无效的时候,base::Bind 会停止task的执行。

base::WeakPtrFactory 用来生成多个 base::WeakPtr 实例,当 Factory 对象销毁,则所有的 WeakPtr 会将他们内部的 "invalidated " 标志置为true,这样会导致绑定在他们身上的task不能分发。(task可以绑定在 WeakPtr 上面),将 factory 作为分发对象的成员变量,你可以拥有这个“自动消除”的属性。

class MyObject {
 public:
  MyObject() : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
  }

  void DoSomething() {
    const int kDelayMS = 100;
    MessageLoop::current()->PostDelayedTask(FROM_HERE,
        base::Bind(&MyObject::DoSomethingLater, weak_factory_.GetWeakPtr()),
        kDelayMS);
  }

  void DoSomethingLater() {
    ...
  }

 private:
  base::WeakPtrFactory<MyObject> weak_factory_;
};

vi. Cancelable request (可取消请求)

可取消请求让它能轻松的向别的线程提出请求,然后那个线程异步给你返回一些数据。就像可撤销的店铺系统一样,我们使用对象,这些对象可以跟踪它的源对象是否存在,如果调用对象已经被删除了,那这个请求也会取消,以避免无效的回调。

像可撤销的店铺系统一样,一个可取消请求的用户拥有一个对象(这里称之为 "Consumer"),它跟踪对象是否还存在,并且会自动删除任何未完成的请求。

class MyClass {
  void MakeRequest() {
    frontend_service->StartRequest(some_input1, some_input2, this,

        // Use base::Unretained(this) if this may cause a refcount cycle.

        base::Bind(&MyClass:RequestComplete, this));  

  }

  void RequestComplete(int status) {
    ...
  }

 private:
  CancelableRequestConsumer consumer_;
};

注意这个MyClass::RequestComplete ,是和 base::Unretained(this) 绑定在一起了。

Consumer 允许你附加额外的数据在一个请求上,使用 CancelableRequestConsumer 将允许你附加任意的数据,这些数据当你调用请求的时候,会被 provider service 返回。当请求取消,数据将会自动销毁。

一个处理请求的服务继承自 CancelableRequestProvider这个对象提供了可以取消轻量级请求的函数,并且将会和 consumers 一起保证在取消的时候,所有东西都被正确的清理。这个 frontend service 只是跟踪并且发送到另外一个线程上的 backend service,它看起来就像这样:

class FrontendService : public CancelableRequestProvider {
  typedef base::Callback<void(int)> RequestCallbackType;

  Handle StartRequest(int some_input1, int some_input2,
      CallbackConsumer* consumer,
      const RequestCallbackType& callback) {
    scoped_refptr< CancelableRequest<FrontendService::RequestCallbackType> >

        request(new CancelableRequest(callback));

    AddRequest(request, consumer);

    // Send the parameters and the request to the backend thread.
    backend_thread_->PostTask(FROM_HERE,
        base::Bind(&BackendService::DoRequest, backend_, request,

                   some_input1, some_input2), 0);   

// The handle will have been set by AddRequest.
    return request->handle();
  }
};

backend service 在另一个线程中运行,它处理并把结果转发回原始的调用者,如下:

class BackendService : public base::RefCountedThreadSafe<BackendService> {
  void DoRequest(
      scoped_refptr< CancelableRequest<FrontendService::RequestCallbackType> >
          request,
      int some_input1, int some_input2) {
    if (request->canceled())
      return;

    ... do your processing ...

    // Execute ForwardResult() like you would do Run() on the base::Callback<>.
    request->ForwardResult(return_value);
  }
};


本文转载自:http://blog.csdn.net/dylgsy/article/details/7845186

共有 人打赏支持
zhangyujsj
粉丝 23
博文 288
码字总数 224241
作品 0
广州
jquery.uploadify插件在chrom浏览器频繁崩溃问题

jquery.uploadify插件在chrom浏览器频繁崩溃问题,一直困扰了好久,网上很多人说是由于chrom缓存的原因,插件初始化时,需要引用js,而chrom的缓存导致没有没有请求到jquery.uploadify-3.1m...

不见鎏年 ⋅ 2015/02/28 ⋅ 0

MATLAB遗传算法学习 Sheffield的gatbx (1)

入门matlab遗传算法的时候,如果你找来一本书,例如雷英杰的《matlab遗传算法工具箱及其应用》,先看概念,看数学原理,看懂了,看例子,再开始写程序,将是一个“漫长”的过程。个人认为最快...

YUYZ ⋅ 2013/08/22 ⋅ 0

记:chrom开启gpu加速

chrom 打开chrome://flags/,搜索gpu,启动Override software rendering list和GPU rasterization image.png 搜索javaScript,启用Experimental JavaScript。 image.png 在显卡设置中,将chr......

海上月_天上月 ⋅ 2017/11/24 ⋅ 0

http协议在使用场景中的流程

通常由http客户端发起请求,创建端口(即手机电脑等使用浏览器打开一个页面) 这时,http服务器就在端口监听客户端请求。 监听到之后,http服务器就向客户端返回状态(如200)和内容(请求的文件,...

天上月丶 ⋅ 2017/12/05 ⋅ 0

js 参数 undefined 小记

今天看jquery发现其框架参数传递很有意思(1.4.0以上版本) (function( window, undefined ) { //中间代码})(window); 一般分析步骤:1、词法分析 1.1 形成临时活动对象 AO {} 1.2 分析形参,...

横着走的螃蟹 ⋅ 2015/05/27 ⋅ 0

关于SESSION问题

今天把一个系统放到360浏览器上测试,突然发现登陆不上了。 页面读取SESSION时失败,但是chrom是好的。 经过检查,发现SESSION已经写入,是在页面跳转后SESSION消失,请问这是什么原因?...

伯洛芒果汁。 ⋅ 2013/01/02 ⋅ 10

video engine load 一直为0 是什么原因?

video engine load 一直为0 是什么原因? 显卡驱动也装了。用chrom播放视频时用gpu-z查看video engine load一直是0.有人遇见过么,就解答!

eric_乔 ⋅ 2017/12/20 ⋅ 0

Chrome 将会对 HTTP Web 表单显示不安全警告

您的 HTTP 网站上有表单、登录的字段和其他输入部分? Chrome 会将其标记为不安全。 主要浏览器开发商(主要是 Chrome 和 Firefox)正致力推动 Web 的 HTTPS,正逐渐采取措施对 HTTP 网页显示...

局长 ⋅ 2017/08/21 ⋅ 7

session中传的值IE和chrom可以获取在firefox和360中获取不到

HttpSession session = ServletActionContext.getRequest().getSession(); String juri=session.getAttribute("juri")+"";直接获取session,session中传的值IE和chrom可以获取在firefox和360......

lxf725 ⋅ 2014/01/22 ⋅ 0

safari的链接type=file兼容性问题

今天解决一个问题,<input type="file" />自定义在IE、Chrom、360、firefox下均可用,唯独在safari下不可用。源码为 <input type="file" name="upload" id="uploadFile" onchange="handleFil......

zdatbit ⋅ 2016/03/09 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vbs 取文件大小 字节

dim namedim fs, s'name = Inputbox("姓名")'msgbox(name)set fs = wscript.createobject("scripting.filesystemobject") 'fs为FSO实例if (fs.folderexists("c:\temp"))......

vga ⋅ 7分钟前 ⋅ 0

高并发之Nginx的限流

首先Nginx的版本号有要求,最低为1.11.5 如果低于这个版本,在Nginx的配置中 upstream web_app { server 到达Ip1:端口 max_conns=10; server 到达Ip2:端口 max_conns=10; } server { listen ...

算法之名 ⋅ 今天 ⋅ 0

Spring | IOC AOP 注解 简单使用

写在前面的话 很久没更新笔记了,有人会抱怨:小冯啊,你是不是在偷懒啊,没有学习了。老哥,真的冤枉:我觉得我自己很菜,还在努力学习呢,正在学习Vue.js做管理系统呢。即便这样,我还是不...

Wenyi_Feng ⋅ 今天 ⋅ 0

博客迁移到 https://www.jianshu.com/u/aa501451a235

博客迁移到 https://www.jianshu.com/u/aa501451a235 本博客不再更新

为为02 ⋅ 今天 ⋅ 0

win10怎么彻底关闭自动更新

win10自带的更新每天都很多,每一次下载都要占用大量网络,而且安装要等得时间也蛮久的。 工具/原料 Win10 方法/步骤 单击左下角开始菜单点击设置图标进入设置界面 在设置窗口中输入“服务”...

阿K1225 ⋅ 今天 ⋅ 0

Elasticsearch 6.3.0 SQL功能使用案例分享

The best elasticsearch highlevel java rest api-----bboss Elasticsearch 6.3.0 官方新推出的SQL检索插件非常不错,本文一个实际案例来介绍其使用方法。 1.代码中的sql检索 @Testpu...

bboss ⋅ 今天 ⋅ 0

informix数据库在linux中的安装以及用java/c/c++访问

一、安装前准备 安装JDK(略) 到IBM官网上下载informix软件:iif.12.10.FC9DE.linux-x86_64.tar放在某个大家都可以访问的目录比如:/mypkg,并解压到该目录下。 我也放到了百度云和天翼云上...

wangxuwei ⋅ 今天 ⋅ 0

PHP语言系统ZBLOG或许无法重现月光博客的闪耀历史[图]

最近在写博客,希望通过自己努力打造一个优秀的教育类主题博客,名动江湖,但是问题来了,现在写博客还有前途吗?面对强大的自媒体站点围剿,还有信心和可能型吗? 至于程序部分,我选择了P...

原创小博客 ⋅ 今天 ⋅ 0

IntelliJ IDEA 2018.1新特性

工欲善其事必先利其器,如果有一款IDE可以让你更高效地专注于开发以及源码阅读,为什么不试一试? 本文转载自:netty技术内幕 3月27日,jetbrains正式发布期待已久的IntelliJ IDEA 2018.1,再...

Romane ⋅ 今天 ⋅ 0

浅谈设计模式之工厂模式

工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻...

佛系程序猿灬 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部