多线程编程是非常复杂的,Chrome的多线程处理有很多值得借鉴的地方。这篇文章是Chromium项目中Threading的设计和说明文档,简单翻译和注释了一下,以帮助理解。
简单总结就是,不要启动太多线程而应该使用专用的公共线程,代码一定要搞清楚运行在哪个线程上,要有一个简单的方式在线程之间进行通讯。
原文地址 http://dev.chromium.org/developers/design-documents/threading
Chrome通过多线程来避免IO延迟阻塞而导致UI响应迟钝。Chrome避免通过互斥锁的方式来实现对象的线程安全,而是让对象只在一个线程里访问,并通过消息在线程之间进行通讯。
虽然可以通过Thread封装类可以很方便的创建线程,但是Chrome鼓励尽可能的使用现有的线程,以避免管理的麻烦。 每个线程都有一个MessageLoop对象用于处理该线程的消息,你可以通过Thread.message_loop()方法获得线程的message loop对象。
现有线程
大部分的工作是在UI线程(即主线程)上完成的,另外的主要线程有,
io_thread 负责进程间通讯(IPC)的消息分发,以及网络资源请求的分发。
file_thread 负责文件读写
db_thread 负责访问本地的SQLite数据库
safe_browsing_thread等等
线程间通讯
如果有代码要在指定线程里执行,则首先必须把代码封装成为一个Task对象(Unit of work模式),然后找到目标线程的MessageLoop对象,调用其PostTask方法。把Task对象放入内部队列之后,PostTask就立即返回了。目标线程依次处理内部队列里的Task对象,调用Task.Run()方法,然后删除Task对象。
比如
class MyTask : public Task {
public:
virtual void Run() {
DoSomething();
}
};
ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, new MyTask);
有封装一个Task其实是很麻烦的事情,特别是我们经常是要调用当前对象的方法。因此Chrome里提供了NewRunnableMethod 辅助函数,可以直接把一个对象的方法封装成一个Task,这样调用PostTask就很简单了。
调用该函数,你需要提供对象指针,对象方法的指针,以及方法的参数(可以多个,类型不限)。因为是跨线程访问,所以该对象必须是线程安全的,并通过引用计数来保证对象在一个线程中访问时不被另一个线程删除了,应该要等到Task执行结束。
比如
class MyObject : public RefCountedThreadSafe<MyObject> {
public:
void DoSomething(const std;:wstring& name) {
thread_->message_loop()->PostTask(FROM_HERE
NewRunnableMethod(this, &MyObject::DoSomethingOnAnotherThread, name));
}
void DoSomethingOnAnotherThread(const std::wstring& 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_;
};
在内部,Chrome是使用Tuple来保存函数调用参数的,使用的是值拷贝,所以最好不要传递裸指针,可以带引用计数的智能指针如scoped_refptr。 比如
class SomeParamObject : public RefCountedThreadSafe<SomeParamObject> {
...
};
class MyObject : public RefCountedThreadSafe<MyObject> {
public:
void DoSomething() {
scoped_refptr<SomeParamObject> param(new SomeParamObject);
thread_->message_loop()->PostTask(FROM_HERE
NewRunnableMethod(this, &MyObject::DoSomethingOnAnotherThread, param));
}
void DoSomethingOnAnotherThread(scoped_refptr<SomeParamObject> param) {
...
}
};
甚至,你可以指定一个对象的析构在特定线程上执行,比如
class MyObject : public RefCountedThreadSafe<MyObject, ChromeThread::DeleteOnIOThread> {
因为Task是异步执行的,就有可能在执行Task的时候,它要访问的对象已经被删除了,这是多线程程序里经常见的一种崩溃模式。所以,最好能在对象被删除前,首先取消所有未执行的相关Task。Chrome的解决办法是,使用工厂对象来创建Task,并把工厂对象作为被调用对象的成员变量。这样,在对象析构时,工厂对象首先被析构并自动取消所有由其创建的Task。使用这种方法,不需要被调用对象使用引用计数来控制生命周期,相对简单。
class MyObject {
public:
MyObject() : factory_(this) {
}
void DoSomething() {
const int kDelayMS = 100;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
factory_.NewRunnableMethod(&MyObject::DoSomethingLater),
kDelayMS);
}
void DoSomethingLater() {
...
}
private:
ScopedRunnableMethodFactory<MyObject> factory_;
};
前面使用工厂对象可以避免对象删除后执行Task引发崩溃,另外还有一种类似的情形就是发出一个请求后又想取消。同样的,Chrome也是使用一个额外的对象来作为跟踪之用,当该对象被删除时自动取消未完成的请求。
class MyClass {
void MakeRequest() {
frontend_service->StartRequest(some_input1, some_input2, this,
NewCallback(this, &MyClass:RequestComplete));
}
void RequestComplete(int status) {
...
}
private:
CancelableRequestConsumer consumer_;
};
处理请求的对象必须是从CancelableRequestProvider派生的,该对象有方法可以取消还没有被执行的请求,并且保证调用者也能得到执行相关清理工作的机会。 如果不需要等待请求的结果,则没有必要使用这个方法,而直接使用Task/RunnableMethod即可。
下面的例子显示的是,一个前台模块接到请求时,直接就把请求转给了后台模块。
class FrontendService : public CancelableRequestProvider {
typedef Callback1::Type RequestCallbackType;
Handle StartRequest(int some_input1, int some_input2,
CallbackConsumer* consumer,
RequestCallbackType* callback) {
scoped_refptr > request(
new CancelableRequest(callback));
AddRequest(request, consumer);
// Send the parameters and the request to the backend thread.
backend_thread_->PostTask(FROM_HERE,
NewRunnableMethod(backend_, & BackendService::DoRequest, request,
some_input1, some_input2), 0);
// The handle will have been set by AddRequest.
return request->handle();
}
};
后台模块是这样返回处理结果的
class BackendService : public RefCountedThreadSafe<BackendService> {
void DoRequest(
scoped_refptr< CancelableRequest<Frontend::RequestCallbackType> >
request,
int some_input1, int some_input2) {
if (request->canceled())
return;
... do your processing ...
// Depending on your typedefs, one of these two forms will be more
// convenient:
request->ForwardResult(Tuple1<int>(return_value));
// -- or -- (inferior in this case)
request->ForwardResult(FrontendService::RequestCallbackType::TupleType(
return_value));
}
};
原文地址 http://dev.chromium.org/developers/design-documents/threading
分享到:
相关推荐
最近对Chromium源码进行了下载与编译,由于花费时间较长而且一些错误感觉并不是很好解决,特地写了一篇文档来记录整个从下载到编译完成的过程,以及我遇到的一些问题的解决方案。
google_chrome浏览器编译过程
Chrome multi-threaded download manager extension,based on Aria2 and AriaNg... Chrome多线程下载扩展。.zip,Chrome multi-threaded download manager extension,based on Aria2 and AriaNg. Chrome多线程下载扩展。
chromeapp编译版本
phantomjs.exe,chromedriver.exe文件改个名字 填上绝对路径 就可以打开多个浏览器对象 .包含 phantomjs.exe,chromedriver.exe 和示例
经过两周探索编译chrome源码,终于编译出了google浏览器的chrome.exe文件,编译方法总结为一篇文档
编译好的Chromedriver,特征已经被抹除目前只有windows10版本 先安装浏览器,然后把驱动chromedriver.exe放入浏览器安装,目录Application目录下即可 demo:...
用于编译chrome的脚本文件,使用shell写成,只需要下载二进制chrome源码,就可以使用这个脚本进行编译。
花了一个多星期摸索出来的用vs2010 编译google v8 引擎的方法 和将它编译成dll的方法,网上文章多,不过说实在的,没一个完全能用的,我这个里面有指导书,有问题请留言
(2)直接安装使用无需自己编译; (3)安装方法:打开Chrome浏览器,输入“chrome://extensions/”打开扩展程序,启用开发者模式,然后把下载下来的压缩包解压后的“ vue_devtools_chrome_5.3.4.crx ”拖到Chrome...
Chrome浏览器的安全机制主要包括:渲染进程沙箱,扩展安全机制,GPU隔离,以及插件的安全防护。这份议题介绍了Chrome这些安全机制的设计思想,基本原理,及发展演变。并对其有效性进行了评估。
chrome chrome
chrome chrome chrome chrome
由于网上的vue-devtools插件编译会出项bg无法加载,这个vue-devtools已经编译好了,只要把他放到chrome插件即可
编译好的vue工具,直接加载到谷歌浏览器,这个是打包了的。可以直接加载到谷歌浏览器中。使用方法,打开谷歌扩展程序,把这个文件拖入就可以了
chrome插件XSwitch chrome插件XSwitch
随着Emscripten的进步以及对OpenModelica的一些调整,Firefox中的模型运行在本机的约1.5倍之内,而在最新的Chrome浏览器中,它们运行在本机的2倍之内。 速度的关键是Emscripten可以编译为asm.js格式,可以很好地对其...
Google Chrome Backup 是一款完整备份Chrome设置(包括历史和扩展), Chrome 历史记录、书签、扩展等等信息的工具,支持 Chromium 及 Portable 版本。 如果使用的是安装版的 Chrome,那么运行 Google Chrome Backup...
Axure RP Chrome 0.6.4 原型工具Chrome扩展插件,支持最新版本Chrome浏览器,安装方法如下(亲测可用): 1、打开谷歌浏览器,在浏览器中地址栏中输入chrome://extensions/,或者点击浏览器的右上角选择更多工具,...