在前面,我们学习了content_shell的流程,串成了一条执行线。从今天开始,我们要开始学习线上的点,由点延伸成为面,最后组成一个学习chromium的网。
首先,我们先介绍chromium中的thread相关知识。
chromium中有哪些线程呢?
1. UI线程。应用程序起来后的主线程。
2. IO线程。负责browser进程和子进程之间的调度线程
3. file线程。不解释
4. db线程。不解释
5.
safe_browsing线程。不清楚
chromium关于thread的
设计原则
有两个:1. 不阻塞UI线程,使得UI有更好的响应。2.不鼓励加锁机制和线程安全对象。
如何做到的呢?那就要考虑chromium中的线程模型了。
1. 不在UI线程作阻塞IO操作,不在IO线程作阻塞IO操作。
2. 线程之间不互相阻塞
3. 许多API都是异步的
为了避免加锁机制,chromium提供的thread模型是在每个线程内保留消息循环,线程之间通过消息传递任务,处理回调函数。
关于多线程的加锁,摘抄如下一段话
多线程编程一直是一件麻烦的事情,线程执行的不确定性,资源的并发访问,一直困扰着众多程序员们。为了解决多线程编程的麻烦,大家想出了很多经典的方案:如:对资源直接加锁,角色模型,CSP,FP等等。他们的思想基本分为两类:一类是对存在并发访问资源直接加锁,第二类是避免资源被并发访问。前者存在许多问题,如死锁,优先级反转等等,而相对来说,后者会好很多,角色模型,CSP和FP就都属于后者,Chrome也是使用后者的思想来实现多线程的。
从开发的角度,我们该如何使用chromium提供的这种线程模型呢?
提供模板
类
base::Callback<>,该类有一个Run函数。其作为函数指针泛型,由base::Bind产生。废话少说,看个例子就什么都明白了
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)>。参数部分请参考
柯里化
如何往一个线程里面放任务呢?
chromium提供的线程模型里面提供PostTask,PostDelayedTask方法。我们以PostTask为例。
void MessageLoop::PostTask(
const tracked_objects::Location& from_here, const base::Closure& task)
其中,
typedef Callback<void(void)> Closure;
因此,可以看到如下一个简单的例子
void WriteToFile(const std::string& filename, const std::string& data);
BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
base::Bind(&WriteToFile, "foo.txt", "hello world!"));
如何让一个类成员函数作为task呢?
同样的道理,只是第一个参数变为类对象。如下例子,不过注意对象通常是引用计数的。
class MyObject : public base::RefCountedThreadSafe<MyObject> {
public:
void DoSomething(const std::string16& name) {
thread_->message_loop()->PostTask(
FROM_HERE, base::Bind(&MyObject::DoSomethingOnAnotherThread, <strong>this</strong>, 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_;
};
注意:base::bind中的参数会被复制到内部存储结构。
取消回调
有两种情形需要取消回调:
1. 需要在一个对象稍后做一些事情,但是到那时,当你的回调调用的时候,对象可能被删除了
2. 情形改变了,有些老任务为了效率考虑需要取消。
取消的重要声明:
取消一个拥有参数的task是危险的,因为可能造成内存泄漏。参考下例
class MyClass {
public:
// Owns |p|.
void DoSomething(AnotherClass* p) {
...
}
WeakPtr<MyClass> AsWeakPtr() {
return weak_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<MyObject> weak_factory_;
};
...
Closure cancelable_closure = Bind(&MyClass::DoSomething, object->AsWeakPtr(), p);
Callback<void(AnotherClass*)> cancelable_callback = Bind(&MyClass::DoSomething, object->AsWeakPtr());
...
void FunctionRunLater(const Closure& cancelable_closure,
const Callback<void(AnotherClass*)>& cancelable_callback) {
...
// Leak memory!
cancelable_closure.Run();
cancelable_callback.Run(p);
}
在FunctionRunLater中,如果object已经被释放的话,两个Run()函数都会造成p泄漏。我们可以使用scoped_ptr来解决这个问题
class MyClass {
public:
void DoSomething(scoped_ptr<AnotherClass> p) {
...
}
...
};
base::WeakPtr 和取消
我们可以使用base::WeakPtr 和 base::WeakPtrFactory,用于保证在object的生命周期之外对应的调用不会发生,甚至不必使用引用计数。base::Bind为base::WeakPtr提供了一种机制,当base::WeakPtr设置为无效的时候,禁止task的执行。其中base::WeakPtrFactory是用于生成
base::WeakPtr对象的。当factory释放的时候,所有base::WeakPtr都会在其内部设置“无效”位,用于任何绑定于他们的task不会被执行。在需要分发消息的对象里面存储一个factory成员变量,在对象析构的时候可以做到
task
自动取消。
但是请注意:这只是在task被发往同一个线程时候才有效。如今还没有比较通用的方法用于多线程之间的任务,参考下一部分关于CancelableTaskTracker的说明。
例子如下
class MyObject {
public:
MyObject() : 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_;
};
可取消的任务
虽然base::WeakPtr很有用,但是他不是线程安全的,不能用于跨线程的任务。这种情况CancelableTaskTracker更为合适。
使用CancelableTaskTracker你可以使用任务返回的id来取消另一个线程的任务。这同样可以被应用在同一个线程。
CancelableTaskTracker相比base::TaskRunner,只是增加了任务的取消功能。
例子如下。
class UserInputHandler : public base::RefCountedThreadSafe<UserInputHandler> {
// Runs on UI thread.
void OnUserInput(Input input) {
CancelPreviousTask();
DBResult* result = new DBResult();
task_id_ = tracker_->PostTaskAndReply(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB),
FROM_HERE,
base::Bind(&LookupHistoryOnDBThread, this, input, result),
base::Bind(&ShowHistoryOnUIThread, this, base::Owned(result)));
}
void CancelPreviousTask() {
tracker_->TryCancel(task_id_);
}
...
private:
CancelableTaskTracker tracker_; // Cancels all pending tasks while destruction.
CancelableTaskTracker::TaskId task_id_;
...
};
任务由于是在另一个线程执行,不能保证一定会被取消。但是可以保证不会crash吧
当我们使用TryCancel()时候,
1. 如果task和reply都没有开始执行,则都会取消
2. 如果task正在执行或者已经执行完,reply
会取消
3. 如果reply正在执行或者已经执行完,无操作
就像base::WeakPtrFactory ,CancelableTaskTracker 在析构的时候将会取消所有的task
chromium提供的线程模型确实是很高明的,没有锁的情况下提供了多种解决方案。
以下的参考很不错。第一个是我本文的主要来源,或者说翻译了部分精华内容。第二个从源码的角度分析了thread。第三个就从实现的角度讲解。
参考:
1.
http://www.chromium.org/developers/design-documents/threading