Overview
Chromium is a very multithreaded product. We try to keep the UI as responsive as possible, and this means not blocking the UI thread with any blocking I/O or other expensive operations. Our approach is to use message passing as the way of communicating between threads. We discourage locking and threadsafe objects. Instead, objects live on only one thread, we pass messages between threads for communication, and we use callback interfaces (implemented by message passing) for most cross-thread requests.
The Thread object is defined in base/thread.h.
In general you should probably use one of the existing threads
described below rather than make new ones. We already have a lot of
threads that are difficult to keep track of. Each thread has a MessageLoop (see base/message_loop.h) that processes messages for that thread. You can get the message loop for a thread using the Thread.message_loop() function.
Existing threads
Most threads are managed by the BrowserProcess object,
which acts as the service manager for the main "browser" process. By
default, everything happens on the UI thread (the main thread where the
application starts up). We have pushed certain classes of processing
into these other threads. It has getters for the following threads:
- io_thread: This thread is somewhat mis-named. It is the dispatcher thread that handles communication between the browser process and all the sub-processes. It is also where all resource rqeuests (web page loads) are dispatched from (see Multi-process Architecture).
- file_thread: A general process thread for file operations. When you want to do blocking filesystem operations (for example, requesting an icon for a file type, or writing downloaded files to disk), dispatch to this thread.
- db_thread: A thread for database operations. For example, the cookie service does sqlite operations on this thread. Note that the two major databases (history and web data) don't use this thread yet.
- safe_browsing_thread
Several components have their own threads:
- History: The history service object has its own thread. This might be merged with the db_thread above. However, we need to be sure that things happen in the correct order -- for example, that cookies are loaded before history since cookies are needed for the first load, and history initialization is long and will block it.
- Web data: The web data service is similar to history, but it stores random data for web browsing such as saved passwords, keywords, and installed applications.
- Proxy service: See
net/http/http_proxy_service.cc. - Automation proxy: This thread is used to communicate with the UI test program driving the app.
Getting stuff to other threads
PostTask
The lowest level of dispatching to another thread is to use the MessageLoop.PostTask and MessageLoop.PostDelayedTask (see base/message_loop.h). PostTask schedules a task to be run on a particular thread. PostDelayedTask schedules a task to be run after a delay on a particular thread. A task is a simple virtual interface that defines one function: void Run(). To process a task, the message loop eventually calls the Task's Run function, and then deletes the task. Both PostTask and PostDelayedTask take a tracked_objects::Location parameter, which is used for lightweight debugging purposes (counts and primitive profiling of pending and completed tasks can be monitored in a debug build via the url about:objects). Generally the macro value FROM_HERE is the appropriate value to use in this parameter.
Note that new tasks go on the message loop's queue, and any delay that is specified is subject to the operating systems' timer resolutions. This means that under Windows, very small timeouts (under 10ms) will likely not be honored (and will be longer). Using a timeout of 0 in PostDelayedTask is equivalent to calling PostTask, and adds no delay beyond queuing delay. PostTask is also used to do something on the current thread "sometime after the current processing returns to the message loop." Such a continuation on the current thread can be used to assure that other time critical tasks are not starved on the current thread.
The following is an example of a class declaring a specialized task that can be posted, along with an example of of how it could be posted to (in this example) the file thread:
class MyTask : public Task {
public:
virtual void Run() {
DoSomething();
}
};
// Note that threads will be NULL in unit testing mode.
Thread* file_thread = g_browser_process->file_thread();
if (file_thread())
file_thread->message_loop()->PostTask(FROM_HERE, new MyTask);
Runnable methods
It is annoying to have to create small task objects for everything
you need to do on another thread. To make it easier, we have a helper
functions called NewRunnableMethod (in base/task.h) to assist you. It will automatically construct a templatized Task to call functions on objects.
These functions take an object, a pointer to a function on that object,
and optional arguments to the function (overrides allow different
number of arguments). The object that PostTask uses must be be a thread-safe reference-counted object. Reference
counting ensures that the object invoked on another thread will stay
alive until the task completes.
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:
Thread* thread_;
};
The arguments in the runnable method will be inferred from the types of the arguments of the destination function. A Tuple (defined in base/tuple.h)
is used to store these parameters and dispatch to the final function.
Object arguments (integers, etc.) will be copied. Pointer values, but
not the object they point to, will be copied, so you probably don't
want to pass pointers.
There is a special TupleTraits (in base/tuple.h) specialization defined for reference arguments so that they will be copied In the example above, the name parameter in DoSomethingOnAnotherThread will actually refer to a copy of the original string stored in the tuple (inside the object generated by NewRunnableMethod). If you have an object that needs special copying semantics, you can define your own TupleTraits specialization for it.
Sometimes, you will want to pass reference-counted objects as parameters (be sure to use RefCountedThreadSafe and not plain RefCounted as the base class for these objects). To ensure that the object lives throughout the entire request, the Task generated by NewRunnableMethod must keep a reference to it. This can be done by passing scoped_refptr as the parameter type:
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) {
...
}
};
Scoped factories
Sometimes you will want to do something "later" on your object. You use PostDelayedTask with a timer value and issue it on the current thread. But what happens if your source object's lifetime is managed by another component, and it gets deleted while the task is waiting to fire? This has been the cause of a number of crashes, especially on shutdown.
In these cases, you can use a RevokableStore (in base/revokable_store.h)
to ensure that any invokes can not outlive the object they are being
invoked on, without using reference counting. A "factory" object will
generate special tasks that know about the factory object. When the
factory is destroyed, all the tasks will have their internal "revoked"
flag set, which will cause them to not dispatch to the original object.
By putting the factory as a member of the object being dispatched to,
you can get automatic canceling.
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_;
};
Cancelable request
A cancelable request makes it easier to make requests to another thread with that thread returning some data to you asynchronously. Like the revokable store system, it uses objects that track whether the originating object is alive. When the calling object is deleted, the request will be canceled to prevent invalid callbacks.
Like the revokable store system, a user of a cancelable request has an object (here, called a "Consumer") that tracks whether it is alive and will auto-cancel any outstanding requests on deleting.
class MyClass {
void MakeRequest() {
frontend_service->StartRequest(some_input1, some_input2, this,
NewCallback(this, &MyClass:RequestComplete));
}
void RequestComplete(int status) {
...
}
private:
CancelableRequestConsumer consumer_;
};
The consumer also allows you to associate extra data with a request. Use CancelableRequestConsumerT
which will allow you to associate arbitrary data with the handle
returned by the provider service when you invoke the request. The data
will be automatically destroyed when the request is canceled.
A service handling requests inherits from CancelableRequestProvider.
This object provides methods for canceling in-flight requests, and will
work with the consumers to make sure everything is cleaned up properly
on cancel. This frontend service just tracks the request and sends it
to a backend service on another thread for actual processing. It would
look like this:
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();
}
};
The backend service runs on another thread. It does processing and forwards the result back to the original caller. It would look like this:
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));
}
};