Original article link
Introduction
We know that GUI applicationsProgramAll are event-driven. Most of these events come from users, such as Keyboard Events, mouse events, or pen point events. There are also some events from the system, such as scheduled events, socket events, and other file events. The application is sleep without any event. 1
Because of this event-driven mechanism, GUI applications require a main loop without exception ). The main loop controls when the application goes to sleep and when it is awakened. The main loop is well implemented, so that the application can work normally and save power. 1
Currently, there are three main ideas for the main cycle design:
- Message Queue + semaphores (semaphore) + sem_wait;
- Event source + select;
- Event source + poll.
For the first design idea, the main cycle constantly extracts messages from the message queue and distributes them to the window. If there is no message in the message queue, the main cycle calls sem_wait to enter the sleep state, wait until there is information to wake up. The main loop of MiniGUI is implemented in this way.
For the second idea, the main loop uses the poll function to listen to the event source. If an event appears, it is processed. Otherwise, it enters the sleep state until an event arrives. The main cycle of GTK + adopts this idea.
The third approach is similar to the second approach. The difference is that the main loop uses the select function to listen to the event source. The main loop of QT uses this approach.
The following three ideas are analyzed one by one based on specific implementations.
Main cycle of MiniGUI
The main loop of the MiniGUI application is similar to that of Win32, which is roughly as follows:
While (getmessage (& MSG, hmainwnd) {dispatchmessage (& MSG );}
In the main cycle of the MiniGUI, it constantly extracts messages from the message queue and distributes them to the Message target (usually a window) until getmessage returns false (receives the wm_quit message, generally, until postquitmessage is called, if there is no message in the queue, the application enters the sleep state.
The advantage of this method is simple and clear, but the defect is obvious. It can only be attached to the message queue, rather than multiple event sources (such as pipelines and sockets) at the same time ). To link multiple event sources, you must use other methods, such as waitformultipleobjects.
For the sleep of the main loop, MiniGUI adopts the semaphore method (sem_wait ). Let's go to the implementation of the getmessage interface:
... Checkagain: lock_msgq (pmsgqueue );... peek message... unlock_msgq (pmsgqueue); If (bwait) {/* no message, wait again. */sem_wait (& pmsgqueue-> wait); goto checkagain ;}
Getmessage calls the sem_wait function to wait for the message queue. If there is no main message loop, it enters the sleep waiting state.
Main cycle 1 of GTK +
In GTK + applications, the main loop is much simpler, but rather unknown.
Each GTK application has the following line:Code:
Gtk_main ();
Many people use GTK + to write a program for a long time. They still think this line of code is mysterious and do not know what it is. Here we will analyze the working principle of gtk_main.
Gtk_main is mainly used to package the main loop of glib, which is divided into three steps:
- Call the initialization function;
- Go to glib main loop;
- Call ~ Initialize the function.
Therefore, after finding out the glib main loop, the implementation of gtk_main will be fully visible. Here we will focus on the implementation of the main loop of glib. The main loop usage mode is roughly as follows:
Loop = g_main_loop_new (null, true); g_main_loop_run (loop); g_main_loop_quit ();
G_main_loop_new creates a main loop object. A main loop object can only be used by one thread, but one thread can have multiple main loop objects. In GTK + applications, a thread uses multiple main loops to implement a modal dialog box. It creates a new main loop in the gtk_dialog_run function, send messages through the main loop until the dialog box is closed.
G_main_loop_run enters the main loop, which will be blocked until it exits. When there is an event, it processes the event and sleeps when there is no event.
G_main_loop_quit is used to exit the main loop, which is equivalent to the postquitmessage function under Win32.
The main feature of glib main loop is that it supports multiple event sources and is very convenient to use. Keyboard and mouse events from users, scheduled events from the system, socket events, and so on. It also supports an event source called idle. Its main purpose is to implement asynchronous events.
The design idea of glib main loop is based on the "Prepare/check/dispatch" mode of poll. Its basic composition is shown in:
The main component of gmainloop is gmaincontext. gmaincontext can be shared among multiple gmainloop, but these gmainloop must be run in the same thread. The modal dialog box mentioned above belongs to this type. Gmaincontext is usually composed of multiple gsources. gsource is the abstraction of the event source. Any event source can be attached to gmaincontext as long as the interface specified by gsource is implemented.
Gsource interface functions include:
- Gboolean (* prepare) (gsource * Source, Gint * timeout _); before going to sleep, in g_main_context_prepare, mainloop calls all source's prepare functions to calculate the minimum timeout time, this time determines the next sleep time.
- Gboolean (* Check) (gsource * Source); after poll is awakened, in g_main_context_check, mainloop calls the check function of all sources to check whether any source is ready. If poll is awakened due to errors or timeout, you do not need to perform dispatch.
- Gboolean (* dispatch) (gsource * Source, gsourcefunc callback, gpointer user_data); when source is ready, in g_main_context_dispatch, mainloop calls the dispatch function of all source to distribute messages.
- Void (* finalize) (gsource * Source); when the source is removed, mainloop calls this function to destroy the source.
The workflow diagram of main loop is as follows:
Let's take a look at several built-in source implementation mechanisms:
Idle is mainly used to Implement Asynchronous events. Its function is similar to the postmessage under Win32. However, it also supports repeated execution, depending on the return value of the User-registered callback function.
- G_idle_prepare sets the timeout value to 0, that is, instant wake-up without entering the sleep state.
- G_idle_check always returns true, indicating that it is ready.
- G_idle_dispatch calls the user-registered callback function.
Timeout is mainly used to implement the timer. It supports one timer and one repetition timer, depending on the return value of the callback function registered by the user.
- G_timeout_prepare calculates the next time-out.
- G_timeout_check checks whether the timeout time has been reached. If so, true is returned; otherwise, false is returned.
- G_timeout_dispatch calls the user-registered callback function.
A thread can add source to its own mainloop, or add source to the mainloop of other threads. When you add source to your mainloop, The mainloop has been awakened, so there is no problem. When the source is added to the mainloop of other threads, the other thread may be sleeping in poll, so you need to wake it up; otherwise, the source may be too late to process. In Linux, this is implemented through the wake_up_pipe pipeline. When mainloop is in poll, it will wait for the wake_up_pipe pipeline in addition to all the sources. To wake up poll, call g_main_context_wakeup_unlocked to write the letter A to the wake_up_pipe.
Main cycle of QT
Qt uses the select-based main loop. The main loop uses the select function to listen to event sources. when an event occurs, it is processed. If no event exists, it waits for sleep.
Shows the event processing module of QT:
Qiniacteventdispatcher encapsulates all event processing interfaces, including processevents, socket registration and cancellation (xxxsocketnotifier), and timer registration and cancellation (xxxtimer.
Different Versions of QT code implement different qiniacteventdispathcer subclasses. Here, I have analyzed the QT Implementation of the X11 platform. It implements the qeventdispathcerunix class and derives the qeventdispatcherx11 class.
The processevents method is used to process events. The select method provides interfaces for listening to events, which actually calls the system's select function.
The QT code is in the src/GUI/kernel and src/corelib/kernel directories.