多线程编程是非常复杂的,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
分享到:
相关推荐
V8使用即时编译(JIT)技术,将JavaScript代码转换为机器码,提高了执行速度。源码中包含了许多优化策略,如垃圾回收机制和TurboFan优化编译器。 3. **Chromium项目**:Chrome浏览器是基于Chromium开源项目的。...
- 处理线程同步:由于CEFSharp运行在单独的线程中,你需要熟悉多线程编程,以确保安全地在UI线程和CEFSharp线程之间进行数据交换。 通过以上步骤和理解,你将能够成功构建一个基于C# WinForm和CEFSharp的简易Chrome...
3. **对象模型**: V8 使用基于哈希表的隐藏类来表示JavaScript对象,这使得对象访问快速且高效。同时,V8支持ECMAScript规范中的所有数据类型,包括原始类型和引用类型。 4. **内存分区**: V8 将内存分为不同的堆区...
在MFC应用的主函数或初始化阶段,调用CEF的初始化函数,如`cefInitialize()`,并设置CEF的相关参数,如多线程模式、命令行参数等。同时,需要为CEF创建一个上下文(`CefApp`对象),并注册自定义的处理程序,如`...
5. **多线程模型**:为了保证性能和用户体验,Chromium在Android上采用了多线程模型,将不同的任务分配到不同的线程。这包括主线程、渲染线程、网络线程等,源码分析可以帮助理解如何有效管理并发。 6. **安全性**...
- **单线程模型**:Node.js采用单线程来处理所有请求,这与传统的多线程模型有所不同,从而在资源利用方面更加高效。 - **非阻塞IO**:通过异步IO机制,Node.js能够在处理IO密集型操作时不会阻塞主线程,从而显著...
这可能包括使用多线程或者多进程模型来提高应用程序的稳定性和性能,以及利用TW3的组件库来实现响应式布局和丰富的用户界面。 【文件名称列表】 - `MainForm.dcu`: 这是Delphi编译后的单元文件,包含`MainForm`类...
Node.js作为一个基于Chrome V8引擎的JavaScript运行环境,主要特点是基于事件循环的非阻塞I/O模型,虽然这样的设计让它在I/O密集型任务中表现优异,但其单线程模型却限制了在CPU密集型任务上的性能表现。为了解决这...
V8引擎是Google开发的一款高性能JavaScript和WebAssembly的开源引擎,它是Chrome浏览器的核心组成部分,为网页和网络应用提供快速的脚本执行环境。V8以其高效、动态优化和即时编译而闻名,使得JavaScript能够在现代...
- **运行时环境**:提供执行程序所需的环境,管理内存分配、线程调度等。 - **库和工具**:提供标准库和其他工具,如调试工具、测试框架等,帮助开发者更高效地完成任务。 #### 架构特点 - **灵活性**:支持多种...
通过理解CEF的基础知识,常用接口和多进程模型,开发者可以构建出高效、安全的应用程序。然而,这只是CEF学习的一部分,深入研究CEF的高级通信技术,如Message传递,将进一步增强你的应用功能和用户体验。
在C++Builder中,还需要处理多线程问题,因为DCEF3库是基于异步模型的。这意味着你需要确保所有的回调都在CEF的消息循环中正确执行,以避免线程安全问题。这可能涉及到使用`CefPostTask`或`CefRunTask`来调度任务。 ...
为了充分利用CEF,开发者应该熟悉Chromium的生命周期管理和多线程模型,因为CEF是基于Chromium的多进程架构,包括浏览器进程、渲染进程和GPU进程等。此外,理解CEF的异步通信机制,如Message Loop和Render Process ...
7. **多线程模型**:浏览器通常采用多线程模型,如GUI渲染线程、网络线程、JS引擎线程和定时器线程等,确保各个任务并发执行而不互相干扰。 深入研究浏览器源码,开发者可以学习到: 1. **事件循环机制**:如何...
2. **单线程**: 与传统的多线程模型不同,NodeJS采用事件驱动和非阻塞I/O模型,主要运行在一个单独的线程中。这减少了线程切换带来的开销,提高了系统效率。尽管是单线程,但NodeJS通过事件循环和回调函数处理并发,...
在传统的多线程模型中,每个连接都需要一个独立的线程来处理,而Node.js则采用单线程和事件循环机制。当一个I/O操作(如读取文件或网络通信)被触发时,不会立即等待结果,而是继续执行其他任务。一旦I/O操作完成,...
为了提高性能,CBE可能使用了C#的异步编程模型,例如`async/await`关键字,这使得程序在等待IO操作完成时可以释放线程,提高资源利用率。 **项目结构与版本控制** 文件名“cbe-master”暗示这是一个Git仓库的主...
4. **JavaScript引擎**:JSCore需要与目标平台的C++运行时和线程模型集成。在Win32和WinCE上,可能需要调整线程调度和内存管理策略。 5. **网络支持**:WebKitNetwork层可能需要针对Win32和WinCE的网络API进行调整...