Skip to content

Instantly share code, notes, and snippets.

@TooTallNate
Created May 26, 2012 18:28
Show Gist options
  • Save TooTallNate/2794861 to your computer and use it in GitHub Desktop.
Save TooTallNate/2794861 to your computer and use it in GitHub Desktop.
Why is Node async?

Why is Node async?

  • Asynchronous code is how you write low-resource, high-concurrency servers. See http://www.kegel.com/c10k.html.
  • Node embracing async from the get-go means that servers are low-resource, high-concurrency by default.
  • Async + JavaScript = Perfect fit for an event loop.
  • When it comes to threads vs event-loop, there are times when either are advantageous.
    • But there are things very hard or impossible to do with threads.
    • WebSockets are difficult to do properly with threads. That's one example where non-blocking IO (async) has a major advantage.
    • RAM usage is another factor, especially when we're talking about the physical hardware required to run your application, which translates to real dollars.
    • It depends on your application in the end:
      • If you're mostly waiting on IO the entire time, an event loop shines.
      • If you're doing CPU-intensive tasks like calculating prime numbers, then you want threads.
  • Evented programming solves a different problem then progressive programming. It is not inherently better, just different.
@cstorey
Copy link

cstorey commented May 26, 2012

@TooTallNate Taking your example of the web-sockets code--I'm really not sure that you'd use that in a situation where you needed to handle messaging patterns that aren't strictly request-response. For example, you can just have your client thread call select(2) to listen for events on the socket or notifications from other threads in your process (although it's relatively common to use a pipe(2) for that).

@russfrank I'd say that Simplicity is a function of the abstractions you have, and how well they're used. You could well make the argument that introducing a callback for each I/O operation can potentially make your call graph more complex if you're not careful.

@zzzcpan True--One thing I think is something of a shame in Node.js land, is that abstractions to make the CPS transformation easier. For example, CPS maps very nicely onto the Deferred Monad. For example, you might know that a lot of Scala libraries (eg: http://akka.io/ or http://dispatch.databinder.net/Dispatch.html ) make use of this pattern extensively. But that said, I missed the part (early in it's history, I understand) when Node.js switched from using Promises by default to callbacks.

Personally, I find the questions around why Node.js has become so popular, and why now more interesting.

@rf
Copy link

rf commented May 26, 2012

@cstorey It absolutely makes it more complex than straight synchronous code. I meant to compare the relative complexity of locking in a threaded system and continuation passing in an evented system.

I'd also like to note that it wasn't my assumption that the concepts in my previous comment were novel to anyone in the discussion; I was just suggesting an alternative way to work the english in the 'why async' argument. That is, by explicitly stating: here is what we are trying to solve; then, here is why our solution works.

@zzzcpan
Copy link

zzzcpan commented May 26, 2012

@russfrank you are forgetting about spawning new threads, joining them, dealing with lock contention, because locking doesn't really work, the whole bunch of synchronization strategies to replace locking, cache lines, thread local memory, affinity and so on.
Multithreaded programming is too complex for most people and should be used in one case only - to implement actor model :)

@rf
Copy link

rf commented May 27, 2012

@zzzcpan absolutely, I was grossly oversimplifying (for brevity's sake).

@apk
Copy link

apk commented May 28, 2012

@russfrank That is not a binary choice. For example, you can use a cooperative threading model (by putting everything under a single lock) which has the big advantage that you don't need to think about concurrency within your code fragments, and has the same disadvantage as node.js' model: you only use one core. We've come there (in a proprietary setting): We used the same asynchronous, callback-driven programming model to do I/O multiplexing, and at some point doing everything in callbacks pointing to the next one becomes just too tedious for some things. (That was in C, compared to that, node.js is much simpler to handle because of garbage collection, and because of true lexical scoping.)

At some time we added the capability to start extra threads (cooperative ones) to be able to do a loop as, well, a loop instead of a data structure with a counter and proper next callback selection. The thread's stack serves as implicit state; and this turned out to be quite helpful.

Only we did it back when single-core was still the norm; node.js is a bit across the grain for current architectures in that regard.

But as we now know, loops are bad, and higher-order functions are good, so there should be a way to emulate such looping with, like, map(), and when we assume that the function argument to map needs to be not a simple function, but one that gets the input value and a result callback, there should be some async_map(fct,list,rescb) to be used like async_map (function (v, cb) { cb (2 * v); }, [1, 2, 3, 4], result_receiver).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment