Multi-Threading TroubleMultithreaded programming is a troublesome thing, I believe many people have deep experience. The uncertainty of execution order, the concurrent access of resources has been plagued many programmers. There are two kinds of methods to solve the problem of multithreading programming: one is to lock the resources of concurrent access directly, and the other is to avoid concurrent access resources; Chromium adopts the second idea to design multi-threading model, and realizes cross-process communication by passing messages between threads.
Design PrinciplesChromium wants to try to keep the UI in a responsive state. This is followed by the following design principles: 1 does not perform any blocking I/O operations on the UI thread, and other time-consuming operations. 2 less lock and thread-safe Object 3 Avoid blocking the I/O thread 4 threads do not block each other 5 when the data is ready to be updated to the shared buffer only with locks (do not use locks during the preparation of the data) chromium creates a number of predefined threads when the program starts, and we try to use a thread as much as possible and not create new threads In order to implement non-blocking code, many of the Chromium APIs are asynchronous.
MessageloopChromium abstracts The message loop into the Messageloop class, each of which has (only) one Messageloop instance. Messageloop provides a Posttask series method that allows you to add tasks to a specific thread. Messageloop installs the "FIFO" sequence to perform the task until the Messageloop:quit message is received and the message loop exits.
Messageloop TypeEach messageloop has a type, all types of messageloop are capable of handling tasks and timers, and the type specifies the type of message that the Messageloop can handle in addition to tasks and timers.
Type_default:The default message loop, which only handles asynchronous tasks and timers timers
type_ui:In addition to handling asynchronous any and timer timers, you can also handle system UI events. The main thread uses this type of messageloop.
Type_io:Support for handling asynchronous I/O events, chromium created by all IO threads that process IPC messages are created by this type of messageloop.
Type_java:Only Android support, the backend implementation is the Java layer of the message processor. It behaves like type_ui, except that the Messagepump factory method on the main thread is not used when it is created.
Type_custom:Provide messagepump at build time
messagepump::D elegateDelegate defines a set of interfaces, implemented by Messageloop, that messagepump through this set of interfaces to trigger Messageloop to perform specific tasks.
MessagepumpMessagepump is used to get a message callback from the system, triggering messageloop to execute the task class. Different types of messageloop have a corresponding messagepump,messagepump implementation that is related to the platform.
Messageloop, Messagepump, delegate relationsMessageloop specific implementation of the platform-related, even on the same platforms, due to the use of different event processing library, its implementation may be different. Chromium encapsulates the platform-related implementation in Messagepump. The implementation of Messagepump provides platform-related asynchronous event handling, while Messageloop provides the basic framework for polling and dispatching asynchronous tasks, which are associated by Messagepump::D elegate abstract interfaces. The main classes and their relationships:
Threads: Create to run below take browser threads as an example to see the process of threading from creation to execution. Call Browsermainloop::createstartuptasks () during kernel initialization to create some startup tasks, including the task "create browser thread", and then call Startup_task_runner_ to perform the startup task:
Startuptask create_threads =base::bind (&browsermainloop::createthreads, base::unretained (this)); Startup_task_ Runner_->addtask (create_threads);
When you perform a task, call Create_threads () and eventually call the system API to create a thread and pass in Thread.threadmain as the entry function. After the thread is created, it starts executing ThreadMain, which first creates a messageloop and then starts the message loop until a quit message is received.
Adding a task task to a thread ends by calling Messageloop:: posttask* () to throw the task into messageloop execution. Each Messageloop instance belongs to a thread, and we first need to know which thread is required to perform the task. We certainly need to maintain the information of the available threads somewhere, such as the correspondence between the name and the thread ID, so that we can identify the thread that needs to perform the given task by a name. The Browserthread class provides a set of static method posttask* to add tasks to browser threads:
Staticbool Posttask (ID identifier, consttracked_objects::location& from_here, constbase::closure& Task); Staticbool Postdelayedtask (ID identifier, consttracked_objects::location& from_here, constbase: :closure& task, base::timedelta delay); Staticbool Postnonnestabletask (ID identifier, consttracked_ objects::location& From_here, constbase::closure& Task);
The first parameter is the identifier of a thread, defined as follows:
Enumid {//The main thread in the browser. UI,//This is the thread of the interacts with the database. DB,//This is the thread of the interacts with the file system. file,//used for file system operations this block user interactions. Responsiveness of this thread affect users. File_user_blocking,//used to launch and terminate Chrome processes. Process_launcher,//This is the thread to handle slow HTTP cache operations. CACHE,//The thread that processes non-blocking IO, i.e. IPC and network. Blocking IO should happen on other threads like DB, FILE,//File_user_blocking and CACHE depending on the usage. IO,//Note:do not add new threads here is only used by a small number of/files. Instead should just use a Thread class and pass its//Messageloopproxy around. Named threads There is only for threads that/is used in many places. This identifier does not represent a thread. InsTead it counts the//number of well-known threads. Insert new well-known threads before this//identifier. Id_count};
adding a task to a DB thread can take code similar to the following:
Browserthread::P osttask ( browserthread::D B, From_here, base::bind ( &geturlthumbnailtask, Url_ String, Top_sites, base::owned (j_callback), Lookup_success_callback, lookup_failed_callback));
Browserthread maintains a global variable g_globals, whose type is browserthreadglobals, defined as follows:
structbrowserthreadglobals {browserthreadglobals (): Blocking_pool (Newbase::sequencedworkerpool (3, "BrowserBlockin G ")) {memset (threads, 0, Browserthread::id_count * sizeof (threads[0])); memset (thread_delegates, 0, Browserthread::id_count * sizeof (thread_delegates[0])); }//This lock protects |threads|. Do not read or modify the that array//without holding this lock. does not block while holding this lock. Base::lock Lock; This array was protected by |lock|. The threads is not a owned by this//array. Typically, the threads is owned on the UI thread by//Browsermainloop. Browserthreadimpl objects remove themselves from the this//array upon destruction. browserthreadimpl* Threads[browserthread::id_count]; Only atomic operations is used on the this array. The delegates is not a owned//by this array, rather by whoever calls Browserthread::setdelegate. browserthreaddelegate* Thread_delegates[browserthread::id_count]; constscoped_refptr<base::sequencedWorkerpool> Blocking_pool;}; Base::lazyinstance<browserthreadglobals>::leaky g_globals = Lazy_instance_initializer;} Namespace
The threads array holds a pointer to the created Browserthread instance. The threads is assigned when initialized:
Voidbrowserthreadimpl::initialize () { browserthreadglobals& globals = g_globals. Get (); Base::autolock Lock (Globals.lock); Dcheck (identifier_ >= 0 && identifier_ < id_count); Dcheck (Globals.threads[identifier_] = = NULL); Globals.threads[identifier_] = this;}
Posttask () adds a task by calling Posttaskhelper (), which internally first obtains the value of G_globals, and then finds the corresponding thread object based on the incoming identity (for example: Browserthread:: DB). You can get the message_loop of the object, and finally call the Message_loop Posttask method to put the task to the appropriate thread execution.
browserthreadglobals& globals = G_globals. Get (); if (!target_thread_outlives_current) globals.lock.Acquire (); base::messageloop* Message_loop = globals.threads[identifier]? Globals.threads[identifier]->message_loop () : NULL; if (message_loop) { if (nestable) { message_loop->postdelayedtask (from_here, task, delay); } else { Message_loop->postnonnestabledelayedtask (from_here, task, delay); } }
How message loops work
Messageloop is actually a loop that pulls the task out of the task queue and executes it, so when all the tasks are done? The most straightforward approach is to keep checking for new tasks in the task queue with a busy wait. Chromium, of course, would not adopt such a clumsy approach. Taking the implementation of Messagepumpdefault as an example, when all tasks have been completed, the wait () function of the event object is executed, waiting for the event or signal wake to continue to loop execution.
Threadrestrictions::scopedallowwait allow_wait;if (Delayed_work_time_.is_null ()) { Event_. Wait ();} else { Timedelta delay = delayed_work_time_-Timeticks::now (); if (Delay > Timedelta ()) { Event_. Timedwait (delay); } else { //It looks like delayed_work_time_ indicates a time of the past, so we //need to call Dodelayedwork now.< C7/>delayed_work_time_ = Timeticks (); }}
How is the wake-up signal sent? Let's take a look at posttask* 's execution process:
When adding a task to a queue, if the task queue is found to be empty, the Schedulework start message loop is called, schedulework the specific implementation is related to the system being used and the event model being used. Or take Messagepumpdefault as an example, it is implemented as follows:
Boolincomingtaskqueue::P ostpendingtask (pendingtask* pending_task) { ... Boolwas_empty = Incoming_queue_.empty (); Incoming_queue_.push (*pending_task); Pending_task->task. Reset (); if (Always_schedule_work_ | | (!message_loop_scheduled_ && Was_empty)) { //Wake up the message loop. Message_loop_->schedulework (); After we ' ve scheduled the message loop, we don't need to does so again //until we know it have processed all of the Work on our queue and are //waiting for more work again. The message loop would always attempt to //reload from the incoming queue before waiting again so we clear this flag< c11/>//in Reloadworkqueue (). Message_loop_scheduled_ = true; } Returntrue;} Voidmessagepumpdefault::schedulework () { ///Since This can is called on any thread, we need to ensure so our RUN
//Loop wakes up. Event_. Signal ();}
To reduce the use of locks and the range of locks, Chromium uses a clever approach: simply put, messageloop maintenance has two queues, one work_queue, and one incoming_queue. The message loop continuously takes the task from Work_queue and executes, and the new join task is put into incoming_queue. When the tasks in Work_queue are finished, copy the Incoming_queue to Work_queue (requires lock). This avoids the need to lock every task that is performed.
Reference
Understanding WebKit and Chromium: message loop
Chromium on the implementation of Chromium main message loop on android:android system
Chrome Learning Note (i): threading model, message loop
Chromium threading model, message loop