Pregunta

I'm trying to understand what an event loop is. Often the explanation is that in an event loop, you do something until you're notified that an event has occurred. You then handle the event and continue doing what you were doing before.

To map the above definition with an example. I have a server which 'listens' in a event loop, and when a socket connection is detected, the data from it gets read and displayed, after which the server resumes/starts listening as it did before.


However, this event happening and us getting notified 'just like that' are to much for me to handle. You can say: "It's not 'just like that' you have to register an event listener". But what's an event listener but a function which for some reason isn't returning. Is it in it's own loop, waiting to be notified when an event happens? Should the event listener also register an event listener? Where does it end?


Events are a nice abstraction to work with, however just an abstraction. I believe that in the end, polling is unavoidable. Perhaps we are not doing it in our code, but the lower levels (the programming language implementation or the OS) are doing it for us.

It basically comes down to the following pseudo code which is running somewhere low enough so it doesn't result in busy waiting:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

This is my understanding of the whole idea, and I would like to hear if this is correct. I'm open in accepting that the whole idea is fundamentally wrong, in which case I would like the correct explanation.

¿Fue útil?

Solución

Most event loops will suspend if there are no events ready, which means the operating system will not give the task any execution time until an event happens.

Say the event is a key being pressed. You might ask if there's a loop somewhere in the operating system checking for keypresses. The answer is no. Keys being pressed generate an interrupt, which is handled asynchronously by the hardware. Likewise for timers, mouse movements, a packet arriving, etc.

In fact, for most operating systems, polling for events is the abstraction. The hardware and OS handle events asynchronously and put them in a queue that can be polled by applications. You only really see true polling at the hardware level in embedded systems, and even there not always.

Otros consejos

I think of an event listener not as a function running its own loop, but as a relay race with the first runner waiting for the starting gun. A significant reason for using events instead of polling is that they are more efficient with CPU cycles. Why? Look at it from the hardware up (rather than the source code down).

Consider a Web server. When your server calls listen() and blocks, your code is taking its place as a relay runner. When the first packet of a new connection arrives, the network card starts the race by interrupting the operating system. The OS runs an interrupt service routine (ISR) that grabs the packet. The ISR passes the baton to a higher-level routine that establishes the connection. Once the connection is alive, that routine passes the baton to listen(), which passes the baton up to your code. At that point, you can do what you want with the connection. For all we know, between races each relay runner could be going to the pub. A strength of the event abstraction is that your code doesn't have to know or care.

Some operating systems include event-handling code that runs its portion of the race, hands off the baton, then loops back to its starting point to wait for the next race to start. In that sense, event handling is optimized polling in lots of concurrent loops. However, there is always an outside trigger that kicks off the process. The event listener is not a function that isn't returning, but a function that is waiting for that external trigger before it runs. Rather than:

while(True):
    do stuff
    check if event has happened (poll)
    do other stuff

I think of this as:

on(some event):    //I got the baton
     do stuff
     signal the next level up    //Pass the baton

and between the signal and the next time the handler runs, there is conceptually no code running or looping.

No. It is not "optimized polling." An event-loop uses interrupt-driven I/O instead of polling.

While, Until, For, etc. loops are polling loops.

"Polling" is the process of repeatedly checking something. Since the loop code executes continuously, and because it is a small, "tight" loop, there is little time for the processor to switch tasks and do anything else. Almost all "hangs," "freezes," "lockups" or whatever you want to call it when the computer becomes unresponsive, are the manifestation of code being stuck in an unintended polling loop. Instrumentation will show 100% CPU usage.

Interrupt-driven event loops are far more efficient than polling loops. Polling is an extremely wasteful use of CPU cycles so every effort is made to eliminate or minimize it.

However, to optimize code quality, most languages try to use the polling loop paradigm as closely as possible for event handing commands since they serve functionally similar purposes within a program. Thus, with polling being the more familiar way to wait for a keypress or something, it is easy for the inexperienced to use it and wind up with a program that may run fine by itself, but nothing else works while it's running. It has "taken over" the machine.

As explained in other answers, in interrupt-driven event handing, essentially a "flag" is set within the CPU and the process is "suspended" (not allowed to run) until that flag is changed by some other process (such as the keyboard driver changing it when the user has pressed a key). If the flag is an actual hardware condition such as a line being "pulled high," it's called an "interrupt" or "hardware interrupt." Most however, are implemented as just a memory address on the CPU or in main memory (RAM) and are called "semaphores."

Semaphores can be changed under software control and so can provide a very fast, simple signalling mechanism between software processes.

Interrupts, however, can only be changed by hardware. The most ubiquitous use of interrupts is the one triggered at regular intervals by the internal clock chip. One of the countless kinds of software actions activated by clock interrupts, is the changing of semaphores.

I've left out a lot but had to stop somewhere. Please ask if you need more details.

Typically the answer is hardware, the OS and background threads you don't control conspire to make it look effortless. The network card receives some data it raises an interrupt to let the CPU know. The OS's interrupt handler handles it. Then a background thread you don't control (that was created by registering for the event and has been sleeping ever since you registered for the event) is woken up by the OS as part of handling the event and runs your event handler.

I am going to go against all of the other answers I see so far and say "yes". I think the other answers are complicating things too much. From a conceptual point of view, all event loops are essentially:

while <the_program_is_running> {
    event=wait_for_next_event()
    process_event(event)
}

If you are trying to understand event loops for the first time, thinking of them as a simple loop will do no harm. Some underlying framework is waiting for the OS to deliver an event, it then routes the event to one or more handlers, then waits for the next event, and so on. That's really all there is to it from an application software perspective.

Not all events triggers are handled in loops. The way I often write my own event engines would be like this:

interface Listener {
    void handle (EventInfo info);
}

List<Listener> registeredListeners

void triggerEvent (EventInfo info) {
    foreach (listener in registeredListeners) { // Memo 1
        listener.handle(info) // the handling may or may not be synchronous... your choice
    }
}

void somethingThatTriggersAnEvent () {
    blah
    blah
    blah
    triggerEvent(someGeneratedEventInfo)
    more blah
}

Note that although Memo 1 is on a loop, the loop is for notifying each listener. The event trigger itself is not necessarily in a loop.

In theory, OS-level key events can use the same technique (though I think they often do polling instead? I'm just speculating here), provided the OS exposes some sort of registerListener API.

Licenciado bajo: CC-BY-SA con atribución
scroll top