线程(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
有几个组件拥有它们自己的线程:
history:history 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会延迟一段时间再执行,一个task被typedef 为 base::Closure,这个定义实际是一个Callback对象,包含了一个Run函数,这个对象被 base::Bind()函数创建,处理一个task,message_loop最终会调用 base::Closure 的 Run 函数,然后会把task 对象的引用 drops。
PostTask 和 PostDelayTask 都有一个tracked_objects::Location 的参数,这个参数是为了满足轻量级的调试用途(task计数、正等待的task以及已完成的task都可以通过Url about:objects被监测到,当然我们需要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
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();
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
...
};
class MyObject : public base::RefCountedThreadSafe
public:
void DoSomething() {
scoped_refptr
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
};
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< CancelableRequestFrontendService::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
void DoRequest(
scoped_refptr< CancelableRequestFrontendService::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);
}
};