Non-blocking IO isn't necessarily faster (at least in Java) [pdf]
mailinator.com
mailinator.com
> Multithreaded server coding is more intuitive - You simply follow the flow of whats going to happen to one client
Is it really? I'd agree if we were talking about something like server spawning a scripting language probably, but in other cases? Most network endpoints are described as state machines. They're not supposed to "flow" - they're responding to input and changing their state. I'd claim that if you implement a protocol with more than 5 states as a properly abstracted state machine, it would be more intuitive than anything that's supposed to emulate "flow". And that's more natural in asynchronous design.
wait_for_response(request, function(response) {
do_something_with_response(response)
...
})
This becomes even more clear in Ruby syntax, where you don't have to nest the reply handler into the wait_xxx function: wait_for_response(request) { |response|
do_something_with_response(response)
...
}
Using monads, you can even abstract the difference away completely. That way, the code looks like ordinary imperative stuff: response <- wait_for_response(request);
do_something_with_response(response)
If your language provides continuations, you can even handle multiple responses that way. Paul Graham did that in Viaweb (http://lib.store.yahoo.net/lib/paulgraham/bbnexcerpts.txt, section "Closures Simulate Subroutines").tl;dr: The asynchronous I/O is only hard to use in languages which lack essential features. In modern languages you can write "intuitive" code that works for both cases, and you are free to switch from multithreaded sync I/O to asynchronous I/O as you like.
The typical web app falls in to the former category: validate input, read from cache, maybe read from database, maybe write to database, generate response.
while (read(...)) {
...
}
instead of: if (read(...) == EWOULDBLOCK) {
yield();
} else {
manage buffer
}Only a tiny bit of your code ever touches send/recv. It never gets EWOULDBLOCK (it's only running because select told it to!). Most of your code deals with entire requests; the state it's saving is largely identical to what a sychronous server deals with; the overhead is in having to hot-potato it through multiple functions.
(Which in turn implies that all the other libraries you are using --at least ones that need to communicate-- need to have pretty deep integration with your "good library," or you're going to be writing that integration yourself.)
(Not that threading is immune here... you need to be careful choosing libraries such that they are re-entrant, etc. But threading has held more mindshare for much longer, and most OSes share a roughly similar model, so they have a large head start on this front.)
Also, the slides give little information about scalability of the two models. From what I know, the performance of non-blocking I/O should degrades more gracefully in presence of high concurrency. I would have liked to see some numbers about that.
For blocking I/O, the O/S needs to wake up a thread. For non-blocking I/O. the event loop returns with a handle being ready, which the user-level code needs to then look up to find the relevant buffer and control structures.
For a benchmark (and some apps) it will come down to cpu cache misses on the loading the relevant state structures.
For scaling (as opposed to 'speed'), it depends how 'cheap' your threads are (for blocking) and if your event loop etc can use multiple cores (for non-blocking).
http://www.cs.ubc.ca/~norm/508/2009W1/ouster95threadsbad.pdf [slide5]
What's Wrong With Threads? - Too hard for most programmers to use. - Even for experts, development is painful.
If you're that afraid of concurrency, just use a single Big Damn Lock to only ever let one touch any shared state at a time. You'll lose out on most of the benefits, but you won't deadlock and won't corrupt things (or at least, not any worse than you can with interdependent or chained events).
Or grab a compiler and a decent beginning textbook (or the internet) and put in your hundred hours or so, to know how to use concurrency sanely (add probably another hundred if you're planning to build you own lock-free underlying data structures).
Synchronization between threads becomes a lot more complex if the connections are interacting with each other, for example for multiplayer games.