This article has been published by the author Wang Rongtao authorized NetEase Cloud community.
Welcome to NetEase Cloud Community, learn more about NetEase Technology product operation experience.
The message loop, which is called an event loop, also known as a run loop or other name, is a programming structure for waiting and dispatching messages, and is the basis for a classic message-driven mechanism, in different systems or mechanisms. For convenience, this article describes a similar structure under each system collectively referred to as the message loop.
Structure
Message Loop, as the name implies, first of all it is a loop, which is the same structure as the for and while of our beginner C language.
Under Windows It might look like this:
MSG msg; BOOL bret;...while (BRet =:: GetMessage (&msg, NULL, 0, 0)) {if (BRet = =-1) {//Handle Error} else { :: TranslateMessage (&MSG); ::D ispatchmessage (&msg); }}
Under iOS It might look like this:
BOOL shouldquit = NO;.. BOOL OK = YES; Nsrunloop *loop = [Nsrunloop currentrunloop];while (ok &&!shouldquit) {ok = [loop runmode:nsdefaultrunloopmod e beforedate:[nsdate distantfuture]];}
The I/O message loops that are implemented with LIBUV may be:
BOOL Should_quit = false;...uv_loop_t *loop = ... while (!should_quit) {Uv_run (loop, uv_run_once);}
In other systems or mechanisms, it has its own unique implementations, but they are broadly similar.
In fact, it is a complete cycle of death before receiving special messages or instructions during normal operation! At the same time, such a structure also determines that it is more of a single-threaded design. Because of this, systems that encapsulate this programming structure (such as iOS) are often not guaranteed or simply not bothered to mention their thread safety. and the multi-threaded shared message loop in the author seems to be the inverse of the design in most scenarios, this article only discusses the message loop on a single thread.
There is an attribute message in front of loop, which further indicates the object to be processed, that is, the message. The message here is a generalized message, which may be UI messages, notifications, I/O events, and so on. So where did the news come from? Where do message loops extract them from? This differs in different systems or mechanisms: there are from the message queue, from the input source/timer source, there are from the asynchronous network, file completion operation notification, as well as from observable object state changes and so on. Here, the source of the message loop extraction message is collectively referred to as the source of the message.
The source does not and cannot actively push the message loop when the message is generated. In the case of Windows messages, an asynchronous window message is generated that is stored on the message queue of the thread that owns the window, and if the message loop does not take any action, it will never be processed. The message loop is extracted from the message queue before it can be fetched and dispatched. This mechanism of extracting messages from a message queue is called a message pump.
Life time
The life of the Message loop begins during the first loop of the thread execution, and finally the loop is broken or the thread is forced to terminate the moment, and between the two is the run-time.
During the run time, the message pump attempts to extract the message from the source, and if the source message is not empty, then the message is immediately taken out and then dispatched for processing. If there is no message within the source, the message loop enters the no-load (idling) phase. Just like a water pump when it's not in the pool. Like a waste of electricity, it wastes almost all of the CPU resources if the message pump works endlessly at no load. In order to solve this problem, the message pump can be self-blocking when it is unloaded, which often needs to be supplied by the source. Another feature of the source is that it wakes up the blocked message pump (exactly the thread of the message loop) after the new message arrives to restore its work. In the above example, the GetMessage, NSRunLoop.runMode:beforeDate: And Uv_run objects have these two characteristics.
The addition of new messages may come from this thread or from other threads, even threads in other processes. In addition, many systems provide revocation or removal of processing messages, such as PeekMessage under Windows, CancelIo can remove the UI messages and I/O actions to be processed, respectively. Nsrunloop.cancelperformselectorswithtarget under iOS: Family method You can undo the selector you want to process.
The process of ending the message loop and ending a normal for, while loop is roughly the same as changing the value of the loop control expression so that it does not meet the conditions of the continuation loop. The difference is that the normal loop is often spontaneous, and the message loop may come from outside requirements and then somehow notify the message loop to exit itself. Another way to end the message loop is to force abort the execution of the thread to which it belongs, which is highly deprecated, of course.
Nesting
The Message loop can be nested (nested), in short, Loop1 another Loop2 in the process of working on a task. Take a look at the following scenarios:
void Runloop () {while (GetMessage (&msg)) {... ProcessMessage (&MSG); ... }} void Start () {runloop ();//Enter loop1}void ProcessMessage (MSG *msg) {... if (Msg->should_do_foo_bar) { Foo (); Runloop (); Enter LOOP2, Nest! Bar (); } ...}
A typical case of nesting is the modal dialog box. After the modal dialog box returns, the statement will not be executed, as in the example above, bar will not be executed until the Runloop is returned, because LOOP1 is blocked after the LOOP2 is started, which leads to a feature of nested message loops: Any time and only one loop is active, The rest is blocked. Another feature of nested message loops is that they belong to one thread, and conversely, the non-threaded message loop cannot be nested.
Nesting of a more obvious pit: if the bar runs on resource R, and R is released during the LOOP2 lifetime, then LOOP1 resumes execution after the end of the LOOP2 life cycle, the first call is bar, and R no longer exists. Bar code may cause crash! if it lacks adequate protection.
Multi-threaded communication
The Message loop makes communication between threads flexible enough.
For example, the process of two threads running a message loop between thread 1 and thread 2 communicates by posting messages to each other's message queue, which is completely asynchronous.
In combination with the message loop nesting technique mentioned earlier, the communication initiation thread can wait for the other party to respond and then perform the subsequent operation without blocking the message processing of this thread. In the case of Foo and bar above, if Foo asynchronously requests a resource, bar handles the received resource, Loop 2 waits until the resource is received and ends immediately, then the three of them look like a synchronous resource request and processing operation on the macro, and during this time thread 1 and thread 2 Message Processing Smooth! This is amazing, and in many cases it's much more useful than a block-like idiot.
However, the message delivery process itself is a cross-threading operation, for scenarios developed in native languages such as C + +, which means that the message queue itself is inherently hidden from the naïve operation of other threads, so it is generally necessary to lock the message queue. In addition, it is generally recommended to hold only weak references to each other's message queue, otherwise it is easy to slip into a circular reference or cause a wild pointer range-imagine if thread 2 exits first, its Message Queuing entity is destroyed, and then if thread 1 tries to pass thread 2 Message Queuing's bare hands are destined to cause disaster.
The communication between multithreading is difficult to deal with is the revocation of the message and the management of resources, but this is not within the scope of this article, if there is time, the author will discuss the issue in the future.
Additional mechanisms
At this point, the message loop described in this article is only dealing with the message itself, but in fact we can add some very useful mechanisms in the message loop, here are the two most commonly used.
The
Idle task is the task that is handled when the message loop is in an empty state. Message loop No-load often means that there are no particularly critical messages to deal with, and this is an excellent time to handle idle tasks, such as sending some background statistics. Using the LIBUV-based I/O message loop as an example, you can add this mechanism by slightly altering it:
class uvmessageloop {public: ...private: bool Should_quit_; bool message_processed_; uv_loop_t *loop _;}; Void uvmessageloop::onuvnotification (uv_poll_t *req, int status, int events) { UVMessageLoop *loop = static_cast<UVMessageLoop *> ( Req->data); ... loop->message_processed_ = true;} Void uvmessageloop::run () { for (;;) { uv_run (loop, uv_run_once); if (Should_quit_) break; if (Message_process_) { // just handled a message continue; } // No news, Handling idle task bool has_idle_task = Doidletasks (); if (should_quit_) break; if (Has_idle_task) { continue; } // idle No task, no more messages, no self-blocking uv_run (loop, uv_run_nowait); }}
Note that the second parameter of the two-Uv_run call in the previous example is different, uv_run_nowait is used to try to extract and process an I/O event from the source but not to return immediately, while Uv_run_once is blocked when there is no event until a new event arrives. It is important to note that when Uv_run handles an event, it will eventually be called synchronously to uvmessageloop::onuvnotification, so that it can check message_processed_ to see if a message has been processed after it returns.
A deferred task (Deferred tasks) is a task that is performed later than the delivery time, such as when the animation is played, and it is used to actually render a frame when the frame time arrives. Continue with the LIBUV-based I/O message loop as an example, you can add this mechanism after the following changes:
class uvmessageloop {public: ...private: bool should_quit_; bool message_processed_; timeticks deferred_task_time_; uv_loop_t *loop_; uv_timer_t * Timer_;}; Void uvmessageloop::onuvnotification (uv_poll_t *req, int status, int events) { UVMessageLoop *loop = static_cast<UVMessageLoop *> ( Req->data); ... loop->message_processed_ = true;} Void uvmessageloop::onuvtimer (Uv_timer_t* handle, int status) { &NBSP, ...} Void uvmessageloop::run () { for (;;) { uv_run (loop, uv_run_once); if (Should_quit_) break; if (Message_process_) { // just processed a message continue; } // No messages, process deferred tasks, and get the time of the next deferred task bool has_deferred_task = dodeferredtasks (&deferred_task_time_); if (Should_quit_) break; if (Has_deferred_task) { continue; } // no deferred tasks, handling idle task bool has_idle_task = doidletasks (); if (should_quit_) break; if (Has_idle_task) { continue; } // No idle task if (Delayed_task_time_.is_null ()) { // no deferred task, one more message, No self-blocking uv_run (Loop_, UV_RUN_ONCE) ; } else { timedelta delay = delayed_task_time_ - timeticks::now (); if (Delay > timedelta ()) { // Set the timer, If no other event arrives before the timer expires, it is unblocked, // then Uv_run will be unblocked due to timed expiry events uv_timer_start (Timer_, onuvtimer, delay. Tomilliseconds (), 0); uv_run (loop_, uv_run_once); uv_timer_stop (Timer_); } else { // the deferred task is not processed in time, into the next round of post-processing delayed_task_time_ = timeticks (); } } if (Should_quit_) break; }}
Because deferred tasks generally have higher precedence than idle tasks, we work with them before idle tasks. In addition, the Deferred_task_time_ records the monotonically increasing time of the next deferred task (such as the clock value of the current thread), when there are no I/O events to process and there are no idle tasks to process, if there are deferred tasks that have not yet expired, Then you need to open a timer on the source to unblock the message pump after the deferred task expires. Therefore, the source to support the deferred task must have a third feature, which is to support timed wakeup.
Resources:
Http://docs.libuv.org/en/latest/loop.html
https://msdn.microsoft.com/en-us/library/windows/desktop/ms644936 (v=vs.85). aspx.aspx)
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/
https://docs.google.com/document/d/1_pJUHO3f3VyRSQjEhKVvUU7NzCyuTCQshZvbWeQiCXU/
NetEase CloudFree Experience Pavilion, 0 cost experience 20+ Cloud products!
more NetEase technology, products, operating experience sharing pleaseClick.
Related articles:
"Recommended" Mid-Autumn Festival welfare |10 This technical book (programming language, data analysis, etc.) Free delivery
Message Loop Principle and application