5 ms·
I think stackful coroutines will always have their fans but the industry is heading towards stackless. The reality is that blocking I/O is a dangerous illusion.
by dnomad 8y ago
I think stackful coroutines will always have their fans but the industry is heading towards stackless. The reality is that blocking I/O is a dangerous illusion. Any time you leave your address space you are fundamentally engaged in an asynchronous exchange. APIs that hide this from people -- even in the name of "ease of use" -- are simply inviting all sorts of unexpected behavior or worse, deadlocks. Stackful coroutines hide their blocking nature from the caller and this makes it very difficult to reason about their behavior.
- zaarn 8y agoYou can get deadlocks in stackless coroutines too. The reason node.js won't deadlock is that it is single threaded. I find stackful coroutines (ie, Go, Rust, Python Async, etc.) quite useful, they don't behave differently to normal functions and in most languages also don't require callbacks (you can exchange channels, generators, etc.). Additionally, such actually threaded implementations of coroutines can take advantage of multiple CPU cores more easily, goroutines can swap quickly in and out of CPU threads. Any waiting action will suspend the goroutine until the IO is available. Unlike in Node.js it won't have to wait until the current synchronous action is done since it can be scheduled in another goroutine at any time (and goroutines automatically yield regularly or you put in manual yields). There is nothing wrong with hiding the nature of the async under the hood of a sync function as long as the abstraction is clean, which in most cases it quite easily can be. For some cases you'll obviously need locks of course, but a simply RWMutex/RWLock can prevent deadlocks easily for 99% of situations where you will need it.
- xfer 8y agoRust allows you to implement bot stackless/stackful coroutines. But the popular library(futures) is stackless and there are proposals to make stackless coroutines convenient(generators RFC).
- beagle3 8y ago> I find stackful coroutines (ie, Go, Rust, Python Async, etc.) quite useful,they don't behave differently to normal functions Python Async is "stackless coroutine", not "stackful" according to the taxonomy given by GP. They also behave quite differently from normal functions.
- dnomad 8y ago> There is nothing wrong with hiding the nature of the async under the hood of a sync function as long as the abstraction is clean, which in most cases it quite easily can be. This is the question and I would disagree. Hiding the asynchronous nature of any given function is a dangerous illusion. Languages that promote it through stackful coroutines ultimately lead to programs that are difficult to reason about and often don't perform well because developers lack any real control over concurrency and are completely at the mercy of an opaque scheduler. (And frankly, even languages like Go which get this 'right' ... don't. Eventually even people who like Go realize Go channels are bad[1].) The answer here I think is going to be higher kinded types a la Rust. Asynchronous is a "fundamental property of time and space" -- ignore it at your own risk. But if the type system can elegantly capture the difference between present-values and values-to-come then you can realize the best of both words: code that reads like a single-thread-of-control but is actually heavily asynchronous. This is why 'await' style stackless coroutines and Futures prove to be so popular. Though I agree more research is needed here. I was disappointed to see the Rust developers (who apparently convinced people to pay them to research this stuff) converge so quickly on stackless coroutines. [1] https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-should-feel-bad/ https://www.jtolio.com/2016/03/go-channels-are-bad-and-you-s...
- zaarn 8y agoNobody forces you to use channels in Go, I use them rather rarely, in 9 out of 10 cases it's to pass around a stop handler for some activity loop. That's where they are quite useful. I think of myself of an older generation so my approach to concurrency is to use locks and fancy data structures and fancy architecture to avoid race conditions. I would suggest looking into how easy it is to write an HTTP handler in Go. That involves plenty of go routines. Each connection is a go routine. Each HTTP request also gets it's own (in HTTP/2 the former and later may not be the same). I don't have to think about the blocking nature of an operation to, for a recent example, fetch and parse a remote webpage, I simply do it. The Go runtime will take care of scheduling the goroutines such that if a new request comes in while I'm a tight loop the HTTP server can continue to handle queries. In JS land however, I can't write tight for loops without potentially blocking up the entire server. You mentioned the solution yourself, hide the async nature of the code. My HTTP code doesn't read like async code until you hit global resources. That's what I essentially want. I do not want to think about async until I need it and when I need it I should be able to pretend it's sync without cognitive overhead; that enables me to efficiently reason about the steps a routine takes before every resource a request uses is released again.
- jtolds 8y agoPlease read http://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/ http://journal.stuffwithstuff.com/2015/02/01/what-color-is-y... It illustrates decisively that a language that doesn't hide async behavior balkanizes libraries into (at least) two camps for everyone's detriment. Fixing this and doing it correctly requires language support, yes (the language's mutexes must work right in the appropriate contexts) but it is much better for the ecosystem to hide the details of async. Making the programmer deal with async nature (even with await, yield, etc) adds additional unnecessary details for the programmer to worry about and makes the program harder to reason about. As for where the industry is headed, Go shows what it's like getting this right.
- dom96 8y agoIt seems to me that a simple solution to this is to default to using async for everything. That way there is no "colours", everything is a single colour (async) and everybody is happy. Can you see a problem with this approach?
- jtolds 8y agoNope! That's what Go does under the hood... and then eliminates keywords like await/yield and promises, because they're always implicitly there.
- jtolds 8y agoOn Linux, nonblocking IO has not always been great, so under the covers Go often translates calls to actually schedule blocking work on a threadpool somewhere, but from the perspective of the programmer, everything is implicitly using await/yield and promises.
- zzzcpan 8y agoWhat color is your function is one the most inadequate articles on concurrency ever written. Even managed to claim shared memory multithreading as superior to all other concurrency models. It's sad that it's popular, shows how little people understand about the subject.