What are the most confusing aspects of using async/await? Is it the implementation (i.e the syntax in JS, C# or Python) or the theory behind it?
What are the most confusing aspects of using async/await? Is it the implementation (i.e the syntax in JS, C# or Python) or the theory behind it?
This is one of the places where the Zen of Python comes to mind:
> Explicit is better than implicit.
Edit: To see what I mean, compare Microsoft's async/await example to Python's:
https://msdn.microsoft.com/en-us/library/mt674882.aspx
https://docs.python.org/3/library/asyncio-dev.html
The Python example tells you how to set up an event loop and add awaitables to it, while Microsoft's "complete example" just has you add async event handlers to a window without showing you anything about setting up an event loop. It wasn't until I saw the event loop being set up in Python that I finally understood what async/await did.
Python asyncio library supports coroutines, callbacks, futures, thread-pool executors... All have similar APIs and dissimilar semantics. For instance, canceling a coroutine is different than canceling a future and different than canceling a thread. (I've heard asynchronous exceptions are bad, but this is just an example.)
Error handling is difficult. You can't observe an error if you don't remember to write code to observe an error.
Also confusing is that coroutines in Python can be driven by any event loop. Which is great! You can write your own. But that will be even more confusing. Erlang has one OTP, Python will have many.
Exactly. Python had a multiple thread and blocking I/O model. That's well understood. Javascript had a single thread callback-based model. That's well understood. When you have both concepts in the same system, things get much more complicated. It's even worse when it's a retrofit.
Consider Python's Queue module. If you call Queue.get(), your thread can block. But the async system doesn't understand that kind of block. (Is that right?) So you've blocked all the async stuff running off that thread.
Go has lightweight threads too, called goroutines. But because it wasn't a retrofit, all lock mechanisms are compatible with lightweight threads. When a goroutine blocks on a lock, its underlying thread is re-dispatched. Python 3.6 isn't set up that way, because async operations were retrofitted in parallel with the existing machinery.
I understand the idea of blocking I/O and skipping that until a function/thread returns.
Of course, many high-performance, I/O intensive applications like web-servers can effectively make use of a combination of multiple threads and I/O demultiplexing as well.
You understand it better than you realize. The idea of a "thread" is that it's a sequentially run context for computation with a bunch of context carried with it (a stack, references to a heap). This sequence of computations can be pre-empted.
When OO systems offer "promises" what they're doing is essentially freeing a thread from the continuity of the computation and leveraging closures or object scope to hold the context. Instead of a thread representing the computation, now threads pick up computations and work on them until they stop for I/O, then resume them when the underlying async I/O layer has work for them.
I can tell you about it in terms of monadic computations or how it relates to other concurrency strategies if you'd like, but it will become more theory-dense rather quickly.
The biggest practical difference is synchronization-free programming. In other words event loops are already always synchronized and you can do whatever you want with shared memory knowing that no other code is going to interfere until you explicitly stop and give up control. This allows for more reliable and more complex concurrent applications with less effort.
Only C (C++, etc.) and Java (C#, etc.) families of languages that share memory between threads can scale better with async. on multicore processors when it comes to network related performance.
Edit: Please comment if you downvote.
Edit: Now I can't comment, the site tells me I'm writing too fast.
JavaScript doesn't have threads so the point is moot.
Only Erlang doesn't share memory between processes.
Nim doesn't share memory either.
In Erlang and Scala you can solve it even more ellegantly using actors and chanels. Go has goroutines and channels. All of these can span multiple cores.
You can prove this to yourself via construction: you can write an interpreter that simulates threads (and even pre-emption) with Promises, Actors, or a more formal monadic style. You can repeat this feat for any single member of the set and if you do it carefully that's a proof by construction.
Related reading, somewhat famous: https://pdfs.semanticscholar.org/2948/a0d014852ba47dd115fcc7...
Essentially what you choose to implement is arbitrary and should be informed by your underlying performance requirements coupled with your preferences.
And, as people have noted: your post seems confused. Everyone benefits somewhat uniformly from asynchronous operations. Java's implementations are often a little bit more tunable because the programmer can provide the threadpool for async workers.
Of all of the languages you named, Erlang is the most distinct as it offers an abstraction (and cost) of 1 heap per actor from the perspective of the garbage collector. This decision (made in a time when GC algorithms were quite a bit worse than they evolved to be) lead Erlang to something they want to keep: an abstraction that makes remote and local computation identical. Other languages that follow in Erlang's footsteps (Pony comes to mind, yay Pony!) do not do things this way.
[1]: Okay EitherT ContT. Sure. Get picky.
P.S., You cannot comment because your overall karma is too low, putting you in a rate-limited category. It generally clears up in a few hours.
However, I do not think there is much more of value to say other than to thank that one poster for a few pretty cool papers!
Here you can read about how async. works in my system: https://github.com/tinspin/rupy/wiki/Fuse
I might not be stroking the HN community in the right direction but trust me, I'm not confused.
That is certainly more accurate, but seemed needlessly confrontational in my first draft, so I opted for the more generic phrase "confused post", which implied maybe the post itself was simply jumbled up and didn't clearly offer your intent to the audience. You did ask after all.
I was trying to give a non-confrontational way to explain why I almost downvoted your post for being very misleading, and opted instead to respond with a slightly more correct post.
HN needs less fighting and more knowledge.
I thought what you were trying to say was, "parallelism is not the same as concurrency" but that it got jumbled up. That is a correct statement.
> trust me, I'm not confused
Sorry but you really are confused about this. You said that Python, Go and Ruby don't have shared memory between threads, but they simply do. All of these languages's concurrency and parallelism models are fundamentally shared-memory, and all even allow completely unsynchronised access to shared memory if you wanted to do that.
If you won't believe me on that, here are five peer-reviewed papers from reputable venues on Ruby alone that talk about the shared memory model it has. Either all of these experts and all the reviewers are mistaken, or you are.
B. Daloze, S. Marr, D. Bonetta, H. Mössenböck. Efficient and Thread-Safe Objects for Dynamically-Typed Languages. In Proceedings of the ACM International Conference on Object Oriented Programming Systems Languages and Applications (OOPSLA), 2016.
C. Ding, B. Gernhardt, P. Li, and M. Hertz. Safe Parallel Programming in an Interpreted Language. In Proceedings of the First Workshop on the High Performance Scripting Languages, 2015.
L. Lu, W. Ji, and M. L. Scott. Dynamic Enforcement of Determinism in a Parallel Scripting Language. In Proceedings of the 35th Conference on Programming Language Design and Implementation (PLDI), 2014.
R. Odaira, J. G. Castanos, and H. Tomari. Eliminating Global Interpreter Locks in Ruby through Hardware Transactional Memory. In Proceedings of the 19th Symposium on Principles and Practice of Parallel Programming (PPoPP), 2014.
W. Ji, L. Lu, and M. L. Scott. TARDIS: Task-level Access Race Detection by Intersecting Sets. In Proceedings of the 4th Workshop on Determinism and Correctness in Parallel Programming (WoDet), 2013.
Well thanks for this, can't wait to sit down & read it.
They might try to work around that, but they will never be able to run one common task to/from many sockets on all cores of one machine at the same time.
Python has a GIL, so only one thread can interact with the Python interpreter at one time. You can have any number of threads running at one time if they release the GIL and don't touch the interpreter (i.e if they are doing IO or calling a C function).
They, however, are still real OS threads, running (waiting for a lock) on separate cores and sharing process memory. That's the definition of a thread. They are not fake threads. They share memory. They run on separate cores (perhaps).
I'm not sure what your last sentence means.
I'm not sure why you're trying to salvage this or what your endgame is here but please reconsider.