Chrom 的线程模型

2015/08/22 18:40
阅读数 208

线程(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);
  }
};


展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部