Is there any other way to implement a “listening” function without an infinite while loop?

后端 未结 2 1484
孤城傲影
孤城傲影 2020-12-06 09:04

I\'ve been thinking a lot about code and libraries like React that automatically, well, react to events as they happen, and was wondering about how all of that is implemente

相关标签:
2条回答
  • 2020-12-06 09:09

    I've written the answer to this question as an aside in another answer. Normally I'd close this question as a duplicate and point to that answer however this is a very different question. The other question asked about javascript performance. In order to answer that I had to first write the answer to this question.

    As such I'm going to do something that's not normally supposed to be done: I'm going to copy part of my answer to another question. So here's my answer:

    Actual events that javascript and node.js waits on requires no looping at all. In fact they require 0% CPU time.

    How asynchronous I/O works (in any programming language)

    Hardware

    If we really need to understand how node (or browser) internals work we must unfortunately first understand how computers work - from the hardware to the operating system. Yes, this is going to be a deep dive so bear with me..

    It all began with the invention of interrupts..

    It was a great invention, but also a Box of Pandora - Edsger Dijkstra

    Yes, the quote above is from the same "Goto considered harmful" Dijkstra. From the very beginning introducing asynchronous operation to computer hardware was considered a very hard topic even for some of the legends in the industry.

    Interrupts was introduced to speed up I/O operations. Rather than needing to poll some input with software in an infinite loop (taking CPU time away from useful work) the hardware will send a signal to the CPU to tell it an event has occurred. The CPU will then suspend the currently running program and execute another program to handle the interrupt - thus we call these functions interrupt handlers. And the word "handler" has stuck all the way up the stack to GUI libraries which call callback functions "event handlers".

    Wikipedia actually has a fairly nice article about interrupts if you're not familiar with it and want to know more: https://en.wikipedia.org/wiki/Interrupt.

    If you've been paying attention you will notice that this concept of an interrupt handler is actually a callback. You configure the CPU to call a function at some later time when an event happens. So even callbacks are not a new concept - it's way older than C.

    OS

    Interrupts make modern operating systems possible. Without interrupts there would be no way for the CPU to temporarily stop your program to run the OS (well, there is cooperative multitasking, but let's ignore that for now). How an OS works is that it sets up a hardware timer in the CPU to trigger an interrupt and then it tells the CPU to execute your program. It is this periodic timer interrupt that runs your OS.

    Apart form the timer, the OS (or rather device drivers) sets up interrupts for I/O. When an I/O event happens the OS will take over your CPU (or one of your CPU in a multi-core system) and checks against its data structure which process it needs to execute next to handle the I/O (this is called preemptive multitasking).

    Everything form keyboard and mouse to storage to network cards use interrupts to tell the system that there is data to be read. Without those interrupts, monitoring all those inputs would take a lot of CPU resources. Interrupts are so important that they are often designed into I/O standards like USB and PCI.

    Processes

    Now that we have a clear picture of this we can understand how node/javascript actually handle I/O and events.

    For I/O, various OSes have various different APIs that provide asynchronous I/O - from overlapped I/O on Windows to poll/epoll on Linux to kqueue on BSD to the cross-platform select(). Node internally uses libuv as a high-level abstraction over these APIs.

    How these APIs work are similar though the details differ. Essentially they provide a function that when called will block your thread until the OS sends an event to it. So yes, even non-blocking I/O blocks your thread. The key here is that blocking I/O will block your thread in multiple places but non-blocking I/O blocks your thread in only one place - where you wait for events.

    Check out my answer to this other question for a more concrete example of how this kind of API works at the C/C++ level: I know that callback function runs asynchronously, but why?

    For GUI events like button click and mouse move the OS just keep track of your mouse and keyboard interrupts then translate them into UI events. This frees your software form needing to know the positions of buttons, windows, icons etc.

    What this allows you to do is design your program in an event-oriented manner. This is similar to how interrupts allow OS designers to implement multitasking. In effect, asynchronous I/O is to frameworks what interrupts are to OSes. It allows javascript to spend exactly 0% CPU time to process (wait for) I/O. This is what makes asynchronous code fast - it's not really faster but does not waste time waiting.

    This answer is fairly long as is so I'll leave links to my answers to other questions that's related to this topic:

    Node js architecture and performance (Note: This answer provides a bit of insight on the relationship of events and threads - tldr: the OS implements threads on top of kernel events)

    Does javascript process using an elastic racetrack algorithm

    how node.js server is better than thread based server

    0 讨论(0)
  • 2020-12-06 09:16

    "listeners" and "subscriptions" are just ideas. Everything can be abstracted with lambdas. Here is one possible implementation -

    const logger =
      // create a new "listener",
      // send any data we "hear" to console.log
      listen(console.log)
    
    // implement so-called "listener"
    const listen = (responder) =>
      x => (responder(x), x)
    
    // run it synchronously
    logger(1)
    logger(2)
    
    // or asynchronously
    setTimeout(_ => logger(3), 2000)
    
    // 1
    // 2
    // some time later...
    // 3

    So let's say we have a

    0 讨论(0)
提交回复
热议问题