多线程编程是非常复杂的,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));
}
};