chromium中的多线程概述

刚开始接触chromium代码的时候,觉得它的多线程非常别扭,有些事情只能在某些线程上才能做,还得bind函数PostTask来PostTask去的。有时候在不同地方获取几个不同的参数,写出的代码支离破碎,PostTask导致逻辑不连贯,非常不习惯这样的。

即使非常讨厌也不得不接受,只有掌握了chromium的多线程,才能随心所欲的写出正确的代码。以前走过很多弯路,再把chromium的多线程文档翻译一遍,写篇博客总结一下。

一般来说我们不必在chromium中创建新的线程,chromium一开始就为我们创建好了几个线程。大多数线程都是属于BrowserProcess对象管理的。

  • ui_thread:程序起来的主线程。
  • io_thread:处理browser进程跟其他子进程通信的线程,网络资源的请求也在通过这个线程调度。
  • file_thread:文件操作线程。
  • db_thread:数据库操作线程,比如cookie数据库的读取。
  • safe_browsing_thread:不知道干嘛的。
  • History:历史记录数据库读取线程。
  • Proxy service:看名字是http代理服务,没怎么接触过。
  • Automation proxy:UI测试用的线程。

我们肯定不能在UI线程上做阻塞I/O的事情,这会导致UI卡死。我们也最好不要在IO线程上做这样的事情。如果阻塞了IO线程,IPC消息就可能也被阻塞了。

为了写出非阻塞的代码,chromium中很多api都是异步执行的。意味着任务在某个线程上执行,当任务执行完毕的时候通过自定义的托管接口返回结果。或者可以用一个base::Callback<>对象进行回调。

base::Callback<>是一个带有run方法的模版,通过base::Bind泛化一个函数指针。下面是一个异步读取文件的例子:

上面代码SomeFunc函数里面base::Bind包装DisplayString的函数指针为base::Callback对象传递到ReadToString里面,on_read就代表了DisplayString函数,通过Run方法去真正执行这个函数。base::Bind甚至把函数和它的参数也绑定成一个base::Callback,当作一个整体传递给其他函数,如:

PostTask

最底层的线程间任务派发是通过MessageLoop.PostTask和MessageLoop.PostDelayedTask实现的。PostTask调度某个任务到某个线程上执行。任务是定义成base::Closure的东西,实际上是base::Callback<void(void)>。PostDelayedTask是延时调度到某个线程上执行。为了执行任务,消息循环会最终会调用base::Closure的Run方法。PostTask和PostDelayedTask都需要一个tracked_objects::Location参数,这个参数是用来调试,通常我们传递FROM_HERE就行了。

上面代码就是从当前线程,把写文件的任务PostTask到文件线程去执行。

base::Bind()

base::Bind()不仅可以绑定一般的函数,也可以绑定类的成员函数。只要第一个参数是类成员函数的类对象,并且这个对象是线程安全的引用技术对象。引用计数确保这个对象在别的线程上执行时还在生命周期中。

如果你能确保对象在别的线程上执行时,对象的生命周期还没有结束,你就可以用base::Unretained()把把对象指针包装起来再传递给base::Bind(),这样就去掉了对象的引用计数。这个方法也可以给base::Bind()传递非引用计数的对象。

base::Bind()中的参数

传递给base::Bind()的参数都被服装掉其内部结构InvokerStorage中保存了。如果你当目标函数后者类方法需要一个const引用的参数,就用base::ConstRef把传递给base::Bind()的参数包装起来。这个也非常注意参数的生命周期,尤其在栈中的对象不要使用base::ConstRef。事实上整个chromium使用base::ConstRef的地方也非常少。
有时候想把引用计数的对象当作参数传递给base::Bind,为了保证其生命周期,需要scoped_refptr去管理参数指针,或者使用make_scoped_refptr()。

如果你想传递的参数不带引用,就用 base::Unretained()包装一下参数,但是你需要自己确保参数的生命周期。

如果你的对象需要在某个特定线程上析构,你可以在继承模版里加入一个类似BrowserThread::DeleteOnIOThread的参数。

取消Callback

取消Callback通常有两个理由:

  • 你需要稍后在对象上做一些事情,但是此时Callback已经运行了,你的对象可能已经别销毁了。
  • 你的输入已经改变了,老任务没必要再执行了。出于性能上的考虑,你需要取消老任务。

如果你有用一个参数的所有权,可能会导致资源泄漏。

在FunctionRunLater,每次Run都会导致p的泄漏。因此需要借助scoped_ptr来管理指针。

base::WeakPtr与取消

可以使用base::WeakPtr和base::WeakPtrFactory确保一个对象销毁后就不会再别调用。base::Bind对于base::WeakPtr有个特别的机制,就是当base::WeakPtr已经无效了就禁止执行任务。base::WeakPtrFactory可以生成base::WeakPtr实例,当base::WeakPtrFactory销毁后,所有的base::WeakPtr实例都变得无效了。这个仅对同一个线程有效。

CancelableTaskTracker

base::WeakPtr可以帮助取消一个任务,但它不是线程安全的,不能用在跨线程的场景中。
CancelableTaskTracker可以帮助我们根据一个TaskID取消一个任务。

总结

写了这么多,chromium中的多线程代码真是恶心极了。

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注