12 ms·
There are three main kinds of async IO. * Event driven with callbacks (`on data`, `on error`, and so on). * Stackless coroutines (async/await). * Stackful co
by ioquatix 8y ago
There are three main kinds of async IO.
* Event driven with callbacks (`on data`, `on error`, and so on).
* Stackless coroutines (async/await).
* Stackful coroutines (no direct semantic changes to code required).
Personally, I like stackful coroutines because the concurrency model has minimal effect on the structure of the code, i.e. it's possible to retrofit existing code without change.
- girvo 8y agoWhat’s a good example implementation of stackful coroutines?
- RussianCow 8y agoAny sort of "green thread" implementation, such as Python's greenlets.
- ufo 8y agoLua coroutines work like this.
- anonacct37 8y agoWould golang count?
- ioquatix 8y agoYes go would fit into the green thread bucket pretty well.
- ioquatix 8y agoThere are two kinds of stackful coroutines - fibers and green threads. The difference is that fibers stay on the thread they were created on, while green threads can transit between OS threads. There have also been projects to cooperatively schedule OS level threads to minimise latency, but that's a bit different. I don't know that many decent green thread implementations [off the top of my head], but an interesting one is yahns: https://yhbt.net/yahns/design_notes.txt https://yhbt.net/yahns/design_notes.txt - to me, green threads have all the benefits of coroutines but all of the downsides of multi-threaded programming. I implemented fiber based concurrency for C++ here: https://github.com/kurocha/async https://github.com/kurocha/async. An example would be https://github.com/kurocha/async-network/blob/d1d2bf12eb2d9b259ecc59f2e4a5cf0756c441e4/test/Async/Network/Test.Socket.cpp#L55 https://github.com/kurocha/async-network/blob/d1d2bf12eb2d9b... - calling read yields the fiber if it would block as implemented here: https://github.com/kurocha/async/blob/2edef4d6990259cc60cc307b6de2ab35b97560f1/source/Async/Protocol/Stream.cpp#L33-L54 https://github.com/kurocha/async/blob/2edef4d6990259cc60cc30... I implemented almost exactly the same model for Ruby here: https://github.com/socketry/async https://github.com/socketry/async and if you are interested in how it works, see these projects which build on it: https://github.com/socketry/async#see-also https://github.com/socketry/async#see-also
- Tarean 8y agoHaskell does async IO by default using green threads. GHC allocates Thread State Objects which completely represent a (green) thread on the heap. A capabillity which represents a cpu core contains a queue of TSO's. If a thread blocks on something it can also move to a different queue which is used for thread synchronization, blocking on computations from other threads, io etc.
- samstave 8y agoOk. Im going to ask you a favor, because i love this comment so much based on how info-dense it is. I challenge you to unpack it not as an ELI5 - but ELIInfant. Please accept. Many shall learn from such.
- ioquatix 8y agoI particularly like this video. Start watching from here: https://www.youtube.com/watch?v=_fu0gx-xseY&feature=youtu.be&t=1m54s https://www.youtube.com/watch?v=_fu0gx-xseY&feature=youtu.be... for the historical context/concepts. He gives an absolutely brilliant overview, better than I can do here with a wall of text. However, he is an advocate for stackless coroutines. They are a good idea in some situations but they still have a semantic overhead unlike stackful coroutines. They may have slightly better performance but at the expense of semantic overhead (async/await, explicit futures/concurrency). I like to think that I'm not advocating for a specific design choice, but for a design choice which makes it possible to write higher level abstractions without the need to expose the underlying concurrency models. If the video doesn't answer all your questions, I'd be happy to answer specific questions.
- rubiquity 8y agoAs I understand it, stackless and stackful coroutines are an abstraction used on top of evented IO. They all use epoll, kqueue, etc. under the covers.
- SideburnsOfDoom 8y agoMy understanding is the same - in C#, async and await are syntactic sugar for callbacks. The code is massively transformed into a state machine for the events.
- ioquatix 8y agoCoroutines are useful in their own right, too.
- dnomad 8y agoI 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.
- beagle3 8y agoThe stackful coroutine route is ideal on paper, but in practice it tends to perform poorly if you retrofit existing code -- because quite often, that existing code depends on blocking i/o or relatively long computations; e.g. opening a file, reading data from a database, or waiting for a TCP connection on the other side of the world to yield data. It is, of course, possible to wrap all of them in a stackful coroutine interface - but that is no longer "retrofitting existing code without change". My impression is that the cumulative experience is to go either with event driven (in C / C++ / Python) or Stackless (in Python / JavaScript) or threaded (in Java), but stackful coroutines very rarely deliver on their promise.