4 ms·
Asynchronous nonblocking interfaces are more general-purpose than synchronous blocking interfaces. I can't speak for this library or Rust specifically, but in m
by jcrites 10y ago
Asynchronous nonblocking interfaces are more general-purpose than synchronous blocking interfaces. I can't speak for this library or Rust specifically, but in my experience well-designed asynchronous libraries allow you to interact with them in a synchronous style as well, if you wish.
Netty is an asynchronous, event-driven network framework for Java, and it's perfectly possible to expose synchronous blocking abstractions on top of it. The mechanism is pretty simple: the asynchronous framework exposes a future representing the result of an operation, and to provide a synchronous interface you simply block on the completion of that future before returning. Client libraries can handle this for you, providing the interface of e.g. a regular blocking HTTP client on top of Netty async IO.
This approach can be convenient, since it's possible for both synchronous and asynchronous style code to coexist easily in the same application. The application designer can incrementally change parts of the application into asynchronous style as performance needs dictate. For example, you might choose to serve typical small RPC requests using blocking workers in a thread pool, but when you need to stream the content of a large file across the network you could use a separate nonblocking worker pool that interacts with both the file system and network asynchronously.
The ability to interact in a blocking way via futures means that asynchronous facilities can serve both synchronous and asynchronous needs, making them the better choice for most frameworks today. While it used to be the case that async IO frameworks took a performance penalty compared to well-implemented sync IO ones, from what I understand that gap has been closed, and the highest performance frameworks are now all async IO. For example, check out the TechEmpower Web Framework Benchmarks. Most or all of the top performers use asynchronous approaches: https://www.techempower.com/benchmarks/#section=data-r13&hw=ph&test=plaintext https://www.techempower.com/benchmarks/#section=data-r13&hw=...
- Animats 10y agoYes, I know, async I/O is the new cool thing. Here's an async I/O program from 1972.[1] John Walker wrote this. EXEC 8 had the IO$ system call, which, unlike IOW$, returned immediately. A "completion routine" was called when the I/O operation finished. Note how similar those libraries are to what's used today, now that people are reading Dijkstra again. The problem, of course, is that a callback system dominates the architecture of the entire program. (When I moved from UNIVAC mainframes to UNIX, things seemed so sequential. No threads. No async I/O.) [1] http://www.fourmilab.ch/documents/univac/fang/ http://www.fourmilab.ch/documents/univac/fang/
- hinkley 10y agoWhere pretty much anything related to concurrency is concerned we've been busy reliving the 70's for most of the last decade. Locking, asynchronous I/O, you name it. Hell even Microsoft had I/O Completion ports back in what, 2003 or so? Or am I wrong and it was a lot earlier? The coolest things in Javascript land were all done by Microsoft first and everyone (me especially) can't bring themselves to acknowledge that.
- kryptiskt 10y agoIO completion ports were introduced in NT 4.0 in 1996.
- fulafel 10y agoIsn't the NT async IO API just a front for a kernel side thread pool though, and may block depending on worker thread availability? They say[1] "if you issue an asynchronous cached read, and the pages are not in memory, the file system driver assumes that you do not want your thread blocked and the request will be handled by a limited pool of worker threads" Things may be different for socket IO, but there Unix had select() much earlier, around 4.2BSD (1983) [1] https://support.microsoft.com/en-us/kb/156932 https://support.microsoft.com/en-us/kb/156932
- blt 10y agoWeb servers and GUI apps are both long-running, event driven programs that need to do IO or slow computations while staying responsive to new events. It's not surprising that they are both well supported by the same programming model. The Win32 API is ugly but the methods of app/OS interaction it supports are fundamentally sound for high performance interactive programs.
- dbaupp 10y agoHuh, so are you implying that hyper should stay synchronous so that it doesn't appear to just be copying things from 40 years ago?! This comment sounds like you think that it was good back then, but now you don't know what to think of a library that is aiming to switch to asynchronous IO and/or don't know why it's a good thing? (It's also not like the comment you're replying to said that async IO is a recent invention, your low-effort sarcasm as a response is unfortunate.)
- saurik 10y agoOne is not more powerful than the other: they are considered "duals" and this has been proven in the literature (back in 1978, no less); most of the supposed downsides of threads are due to people assuming a specific implementation of threads (many if not most of which suck). Here are some papers that would normally be assigned reading in a graduate level Computer Science course in Operating Systems as background reference. https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc70202c840d71cac.pdf https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc7... http://capriccio.cs.berkeley.edu/pubs/threads-hotos-2003.pdf http://capriccio.cs.berkeley.edu/pubs/threads-hotos-2003.pdf But like, it should be obvious: with a lightweight co-routine library you can convert anything that is synchronous into something that is asynchronous with no more if not less overhead than you would get from context switches as you are forced to incur from returning and calling a new function to implement event processing. This is no more onerous than using that same co-routine library to implement blocking on a future (to convert an asynchronous API into a synchronous one).
- jcrites 10y agoThe fact that two styles or concepts are formally dual does not make them equally practical or useful in all circumstances. Consider: in calling conventions, the continuation passing style is dual to the "direct" calling convention (i.e., call stack with return values); the call-by-name style is dual to call-by-value style; Lambda Calculus and Turing Machines are dual in their ability to compute all effectively calculable functions. These dualities do not mean it's equally practical to build systems in both ways. Sometimes one approach ends up being more practically useful. Most programmers prefer to use the direct calling convention, and find complex continuation passing style to be difficult to read and maintain. JavaScript programmers may be familiar with the pain of CPS due to excessive use of callbacks (not strictly CPS but has similar drawbacks). Similarly, writing code purely in call-by-name style can be confusing and have difficult to predict performance impacts (e.g., Haskell lazy evaluation semantics). In their article the article "On the Duality of Operating System Structures", Lauer and Needham present a similar conclusion [3]: > "The principal conclusion we will draw from these observations is that the considerations for choosing which model to adopt in a given system [...] [are] a function of which set of primitive operations and mechanisms are easier to build or better suited to the constraints imposed by the machine architecture and hardware." In that passage they are describing message passing vs. procedure call systems, and I interpret this to be their acknowledgment that, though the systems are dual, one architecture or another is more appropriate in certain circumstances. Getting back to our original topic: this thread was about the decision of a Rust library to offer async or sync IO as its choice of primary primitive. I think async is the better general-purpose choice, because it's clean, simply, and straightforward to expose a synchronous interface on top of an async interfaces with futures; and the other way around is messy and difficult. Can you elaborate on the lightweight co-routine library that can be used to convert anything synchronous into async? I'm curious about that, because Rust previously had support for coroutines (green threads), and decided to remove them due to a number of problems [1]. Meanwhile, Rust developers were able to devise a zero-cost futures abstraction on top of asynchronous IO [2]. Unlikely the problematic green threads strategy, this approach doesn't impose any complicated constraints on the systems that use it (FFI requirements), and doesn't add runtime overhead. What co-routine library would you recommend that avoids the downsides in [1]? [1] https://github.com/aturon/rfcs/blob/remove-runtime/active/0000-remove-runtime.md https://github.com/aturon/rfcs/blob/remove-runtime/active/00... describes some pretty tricky challenges. [2] https://aturon.github.io/blog/2016/08/11/futures/ https://aturon.github.io/blog/2016/08/11/futures/ [3] https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc70202c840d71cac.pdf https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc7...