OverviewChromium 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
Existing threads
Most threads are managed by the
Several components have their own threads:
Keeping the browser responsiveAs hinted in the overview, we avoid doing any blocking I/O on the UI thread to keep the UI responsive. Less apparent is that we also need to avoid blocking I/O on the IO thread. The reason is that if we block it for an expensive operation, say disk access, then IPC messages don't get processed. The effect is that the user can't interact with a page. Note that asynchronous/overlapped IO are fine. Another thing to watch out for is to not block threads on one another. Locks should only be used to swap in a shared data structure that can be accessed on multiple threads. If one thread updates it based on expensive computation or through disk access, then that slow work should be done without holding on to the lock. Only when the result is available should the lock be used to swap in the new data. An example of this is in PluginList::LoadPlugins (src/webkit//plugins/npapi/plugin_list.cc). If you must use locks, here are some best practices and pitfalls to avoid. In order to write non-blocking code, many APIs in Chromium are asynchronous. Usually this means that they either need to be executed on a particular thread and will return results via a custom delegate interface, or they take a base::Callback<> object that is called when the requested operation is completed. Executing work on a specific thread is covered in the PostTask section below.
Getting stuff to other threads
base::Callback<>, Async APIs, and CurryingA base::Callback<> (see the docs in callback.h) is templated class with a Run() method. It is a generalization of a function pointer and is created by a call to base::Bind. Async APIs often will take a base::Callback<> as a means to asynchronously return the results of an operation. Here is an example of a hypothetical FileRead API.
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));
};
In the example above, base::Bind takes the function pointer &DisplayString and turns it into a base::Callback<void(const std::string& result)>. The type of the generated base::Callback<> is inferred from the arguments. Why not just pass the function pointer directly? The reason is base::Bind allows the caller to adapt function interfaces and/or attach extra context via Currying (http://en.wikipedia.org/wiki/Currying). For instance, if we had a utility function DisplayStringWithPrefix that took an extra argument with the prefix, we use base::Bind to adapt the interface as follows.
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: "));
};
This can be used in lieu of creating an adapter functions a small classes that holds prefix as a member variable. Notice also that the "MyPrefix: " argument is actually a const char*, while DisplayStringWithPrefix actually wants a const std::string&. Like normal function dispatch, base::Bind, will coerce parameters types if possible. See "How arguments are handled by base::Bind()" below for more details about argument storage, copying, and special handling of references.
The lowest level of dispatching to another thread is to use the Note that new tasks go on the message loop's queue, and any delay that is specified is subject to the operating system's 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 this thread. The following is an example of a creating a task for a function and posting it to another thread (in this example, the file thread):
void WriteToFile(const std::string& filename, const std::string& data); You should always use BrowserThread to post tasks between threads. Never cache MessageLoop pointers as it can cause bugs such as the pointers being deleted while you're still holding on to them. More information can be found here.
base::Bind() and class methods.The base::Bind() API also supports invoking class methods as well. The syntax is very similar to calling base::Bind() on a function, except the first argument should be the object the method belongs to. By default, the object that
class MyObject : public base::RefCountedThreadSafe<MyObject> {
} private: Thread* thread_; If you have external synchronization structures that can completely insure that an object will always be alive while the task is waiting to execute, you can wrap the object pointer with base::Unretained() when calling base::Bind() to disable the refcounting. This will also allow using base::Bind() on classes that are not refcounted. Be careful when doing this!
The arguments given to base::Bind() are copied into an internal InvokerStorage structure object (defined in base/bind_internal.h). When the function is finally executed, it will see copies of the arguments. This is important if your target function or method takes a const reference; the reference will be to a copy of the argument. If you need a reference to the original argument, you can wrap the argument with base::ConstRef(). Use this carefully as it is likely dangerous if target of the reference cannot be guaranteed to live past when the task is executed. In particular, it is almost never safe to use base::ConstRef() to a variable on the stack unless you can guarantee the stack frame will not be invalidated until the asynchronous task finishes.
Sometimes, you will want to pass reference-counted objects as parameters (be sure to use
class SomeParamObject : public base::RefCountedThreadSafe<SomeParamObject> {
If you want to pass the object without taking a reference on it, wrap the argument with base::Unretained(). Again, using this means there are external guarantees on the lifetime of the object, so tread carefully! If your object has a non-trivial destructor that needs to run on a specific thread, you can use the following trait. This is needed since timing races could lead to a task completing execution before the code that posted it has unwound the stack.
There are 2 major reasons to cancel a task (in the form of a Callback):
See following about different approaches for cancellation.
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);
}
In FunctionRunLater, both Run() calls will leak p when object is already destructed. Using scoped_ptr can fix the bug.class MyClass {
base::WeakPtr and Cancellation [NOT THREAD SAFE]You can use a NOTE: This only works when the task is posted to the same thread. Currently there is not a general solution that works for tasks posted to other threads. See the next section about CancelableTaskTracker for an alternative solution.
class MyObject {
CancelableTaskTrackerWhile base::WeakPtr is very helpful to cancel a task, it is not thread safe so can not be used to cancel tasks running on another thread. This is sometimes a performance critical requirement. E.g. We need to cancel database lookup task on DB thread when user changes inputed text. In this kind of situationCancelableTaskTracker is appropriate.
With CancelableTaskTracker you can cancel a single task with returned TaskId. This is another reason to use CancelableTaskTracker instead of base::WeakPtr, even in a single thread context.
CancelableTaskTracker has 2 Post methods doing the same thing as the ones in base::TaskRunner, with additional cancellation support. 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)));
}
Since task runs on other threads, there's no guarantee it can be successfully canceled.When
TryCancel() is called:
Like
base::WeakPtrFactory, CancelableTaskTracker will cancel all tasks on destruction.Cancelable request (DEPRECATED)Note. Cancelable request is deprecated. Please do not use it in new code. For canceling tasks running on the same thread, use WeakPtr. For canceling tasks running on a different thread, use CancelableTaskTracker.
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 RequestComplete(int status) {
Note that the MyClass::RequestComplete, is bounded with base::Unretained(this) here.
The consumer also allows you to associate extra data with a request. Use
A service handling requests inherits from
class FrontendService : public CancelableRequestProvider {
request(new CancelableRequest(callback));
AddRequest(request, consumer); // The handle will have been set by AddRequest. 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 base::RefCountedThreadSafe<BackendService> {
|
