12 ms·
The magic of asyncio explained
- ioquatix 8y agoHere is a comparison of `asyncio` (Python), `async` (Ruby) and Go: https://github.com/socketry/async-await/tree/master/examples/port_scanner https://github.com/socketry/async-await/tree/master/examples... I wrote a similar article but for Ruby: https://www.codeotaku.com/journal/2018-06/asynchronous-ruby/index https://www.codeotaku.com/journal/2018-06/asynchronous-ruby/... Yes, it's a good model for many use cases. One thing I wondered about Ruby, is it really necessary to have the `await` keyword?
- kasbah 8y ago`await` is useful since with the absence of it you can schedule multiple tasks concurrently. In JS: var task1 = someAsyncTask1() var task2 = someAsyncTask2() await Promise.all([task1, task2]) If `await` was implicit then task2 would wait for task1 to finish.
- sadgit 8y agoI’m still bummed that Python took this direction. Maybe introducing new keywords into the language for event loop concurrency was Python’s way of satisfying “explicit is better than implicit” but i can’s shake the feeling that callback passing and generator coroutines are a fad that is complex enough to occupy the imagination of a generation of programmers while offering little benefit compared to green threads.
- carreau 8y agoNote that the asycn/await syntax, coroutines, an asyncio are 3 independents part. If you do not like callback and Future have a look at trio[1] that takes a quite different approach. http://trio.readthedocs.io/en/latest/ http://trio.readthedocs.io/en/latest/
- manaskarekar 8y agoThis might be interesting reading. Comments on rust's approach to async from green thread. https://aturon.github.io/blog/2016/08/11/futures/ https://aturon.github.io/blog/2016/08/11/futures/
- steveklabnik 8y agoAnd we're going further, with async/await http://aturon.github.io/2018/04/24/async-borrowing/ http://aturon.github.io/2018/04/24/async-borrowing/
- nemothekid 8y agoOne of the key reasons Rust took this approach is because Rust's implementation is zero cost - it doesn't require a runtime to implement. It is potentially very efficient, and thanks to Rust's other guarantees its very safe to use. It's as close to bare metal as you can get for a async framework and as a result its incredibly efficient. However, to me, the implementation is a lot complex that green threads. The Future's crate has had a lot of churn, and when I first dabbled in it a while back, it was one of the first few times I struggled to understand what the compiler errors even meant as the types were so deep. Compared to golang's 'go', futures are harder to understand, plus you have to rewrite all your networking/blocking code to be compatible (Python would still likely have to do the same, but I think it could be done in such a way that if you were using the system provided networking libraries, you could get compatibility for "free"). Python doesn't benefit from the Rust benefits explained in that article. Python already has a garbage collector and runtime. Python is single threaded. I don't follow the Python language to have a well informed opinion on why they went with futures, but I doubt its close to the reasoning that Rust chose.
- smittywerben 8y ago> Python is single threaded. That's not true, there are multiple threads in Python i.e. zlib from the standard library. People want unsafe code to communicate concurrently through the Python thread state. I'll let someone else tackle that one.
- shawn 8y agoEffing thank you. I don't think most people realize just how convenient green threads are. It kills me to see devs stuck in the local maximum of callback hell. That sad, https://www.usenix.org/system/files/conference/atc12/atc12-final206.pdf https://www.usenix.org/system/files/conference/atc12/atc12-f... raises some interesting points in defense of one-way RPC. The key is not to allow returns.
- kbaker 8y agoIsn't Python's async/await syntax an implementation of green threads? I mean using await is almost exactly the cooperative scheduling idea. The article may use Futures and callbacks but you can just as easily do something like: result = await fake_network_request('one')
- toast0 8y agoThey're sort of similar, and you can probably get the same work done in either system, but I think real threading (green or otherwise), may leave you with less cognitive load. Spawning a thread may be complex, and thinking about how the threads are scheduled is often complex, but what each thread does can be very simple -- and you don't have to think about 'long running things need to be futured/awaited', you just do things in a straightforward way in the thread (caveat: slightly less straightforward if you need thread actions to be cancellable). Green threads may be running an event loop underneath, but it's a useful abstraction in many contexts.
- kjeetgill 8y agoI agree it's always left a bag taste in my mouth. I loved generators and yield/yield from, but I was stuck on 2.7 for a long time so I never quite understood the motivation for async/await over them. One issue is that it "reifys" the "colored function" problem that green threads like goroutines don't have! Side note: Java world is working on green threads/fibers for the JVM in Project Loom. [0]: http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.html http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.htm...
- cshenton 8y agoFor me it had the opposite effect. Working with async-await syntax was the last straw that made me finally go "there's got to be a better way" and find a language can handle concurrency without the semantic overhead (in my case Go, but there are others).
- nerdwaller 8y agoHaving worked in asyncio for a bit I don’t entirely follow this (it truly could just be familiarity), very little of asyncio (especially post `async/await` were introduced in the language) is callback based and reads more procedural. Regarding generator coroutines it feels like a natural evolution of the language. Given that yield previously suspended the current function’s state providing value(s) to the closure, it only makes sense that yield (on the producer side)/await (on the consumer side) does the same thing but in an event loop based context. I can’t speak deeply enough about green threads, but from my understanding there’s much less magic (as you cite “explicit”) in an async/await world vs the magic (“implicit”) world of green threads. async def thing(): print(‘before’) await asyncio.sleep(0) print(‘after’) Vs def thing(): print(‘before’) gevent.sleep(0) print(‘after’) There’s nothing clear in the latter when something yields or otherwise passes control flow. Having worked in a few evented systems, I find the explicit shift to the runtime is valuable.
- BlackFly 8y agoThe cooperating part of this concurrency model is the complicated part. Consider how you would go about making an orm like sqlalchemy cooperate. Now you have to access properties like this: name = await account.user.name since a lookup may have to occur. This is extremely unnatural and would be better if you could just avoid writing await yet still depend on it being concurrent without blocking your event loop. The fact that the caller needs to understand that the callee supports this form of concurrency is an abstraction inversion in my opinion. Python forces this concurrency to be explicit, but it would be more powerful and more natural if it were implicit: name = account.user.name
- iamforreal 8y agoI've gone back and forth on this so much. On the one hand, it's really annoying when your client library doesn't actually support asyncio compatible code (ex libraries which perform synchronous network or disk reads/writes), and you have to wrap everything in an executor. On the other hand, making it explicit ensures I'm actually doing things async. "Leaf" functions with an async containing no await is now a red flag to me. It's a mental tax to remember that I may actually be returning a future instead of the result of a future (similar to how you can return a function but not the result of that function being executed, or a non materialized generator), and having to call 'await x' instead of just assigning x kind of violates 'do what I mean'. In the end, async is (relatively) difficult, so I appreciate the enforced explicitness.
- sebcat 8y ago> i can’s shake the feeling that callback passing and generator coroutines are a fad [...] Callback passing and coroutines are well known techniques that's been around for a while. Generators are just coroutines that yield to their parent. I remember using these concepts in C and Tcl ~15 years ago[1] and they were well known then. According to wikipedia (citing Knuth), the term coroutine was coined in the late 50's by Conway. Callback passing and coroutines suits some problems well. Sure there are situations where they do not fit and if people use a hammer for all of their problems they will create new ones. I wouldn't call it a fad, though the techniques may be a bit hyped up in some circles. [1] I don't remember when Tcl got the coroutine package, that may have been later
- azeirah 8y agoPerhaps asyncio is just a bit more low-level than we're used to in Python. Maybe we'll end up with something analogous to the "requests" library, but then for asyncio...
- sametmax 8y agoIt's doesn't have to be complex though. It's complex because the asyncio API is terrible. It exposes loop/task factory and life cycle way to much, and shows it off in docs and tutorials. Hell, we had to wait for 3.7 to get asyncio.run() ! Even the bridge with threads, which is a fantastic feature, has a weird API: await = asyncio.get_event_loop().run_in_executor(None, callback) Also, tutorials and docs give terrible advices. They tell you to run_forever() instead of run_until_complete() and forget about telling you to activate debug mode. They also completly ignore the most important function of all: asyncio.gather(). asyncio can become a great thing, all the foundational concepts are good. In it's current form, though, it's terrible. What we need is a better API and better doc. A lot of people are currently understanding this and trying to fix it. Nathaniel J. Smith is creating trio, a much simpler, saner alternative to asyncio: https://github.com/python-trio/trio https://github.com/python-trio/trio Yury Selivanov is fixing the stdlib, and experiments with better concepts on uvloop first to integrated them later. E.G: Python 3.8 should have trio's nurseries integrated in stdlib. Personally, I don't want to wait for 3.8, and I certainly don't want the ecosystem to be fragmented between asyncio, trio or even curio. We already had the problem with twisted, tornado and gevent before. So I'm working on syntaxic sugar on top of asyncio: https://github.com/Tygs/ayo https://github.com/Tygs/ayo The goal is to make the API clean, easy to use, and that enforced the best practices, but stay 100% compatible with asyncio (it uses it everywhere) and it's ecosystem so that we don't get yet-another-island. It's very much a work in progress, but I think it demonstrate the main idea: asyncio is pretty good already, it just needs a little love.
- jrs95 8y agoI’m not an expert on this but it seems like they just didn’t want to find a way to give you green threads without GIL. This had already been done in another Python implementation: https://en.m.wikipedia.org/wiki/Stackless_Python https://en.m.wikipedia.org/wiki/Stackless_Python Stackless has a proven model. Basically the same model as goroutines and channels. It’s the reason EVE Online is able to run its primary game server in Python with such a large number of users.
- protonfish 8y agoI wish they had implemented parallelism based on the actor model. It seems like the perfect high-level abstraction for managing parallel processes. All of the asyncio stuff feels too fine-grained for Python-style development.
- meken 8y agoI really liked this article. It's by far the most concise explanation of asyncio in python that I've come across. Also, great use of little "quoted" statements throughout that encourage you to stop and really understand what was said before moving on (these probably have a special name). Bravo!
- greyman 8y agoPython is my language of first choice, but I must say that I am not that thrilled how this multithreading ended up. There are many tutorials about the topic promising to explain how it works, usually in the form of "simple introduction". But when one tries to implement something production-ready, with correct error handling etc., things starts to complicate pretty quickly; at least that was my experience. I don't want to accuse anyone specifically, but most of the tutorials I saw seems to portrait it in a way, that it looks easier than it actually is. Ultimately, my company decided, that instead of fighting with asyncio, certain projects will switch to Go.
- apoorvgarg 8y agoThanks for the perspective (especially for me, since Go is my primary language.) Just to clarify, the primary target audience for the article is anyone who is getting started with asyncio (although I know of people who have been using asyncio, but don't really understand whats going on).
- sachin18590 8y agoI am curious what considerations your company had before switching some projects to Go. Python multithreading has been an issue for us as well. While asyncio looked good in the tutorials, gevent was much easier to work with. However, we still face multiple issues moving our celery workers to gevent and I am not sure if there is a better production friendly alternative for celery-gevent in python.
- greyman 8y agoI am actually not that familiar with that decision, since I still work on python projects (but those don't require multithreading). But the guys who work on Go projects mostly cited the following advantages: 1) good performance, since it is compiled, 2) easier deployment, since it compiles into single statically-linked file, and 3) multithreading is backed into the language. Instead of gevent, I had quite a good experience with concurrent.futures; but I used it only for simple things like download multiple URLs in parallel, etc. Anyway, I can't help, but in retrospective all this multithreading looks to me a bit like being hacked into python language as an afterthought.
- _Codemonkeyism 8y agoWasn't there a language where every call was async? Instead of async ... A/returing Future[A] it did/would return A from method calls. If it didn't exist, one can imagine one. A.x = 3 would be wrapped in A.map(_.x = 3) etc. So you write code that would be executed when you finally await a value. No more red/blue world. Would probably need coroutines instead of threads for executing.
- icebraining 8y agoIsn't Haskell somewhat like that, due to being lazy by default?
- _Codemonkeyism 8y agoIn Haskell you'd have the type signature everywhere I think, mostly as a monad transformer.
- _Codemonkeyism 8y agoWow, how could this be voted down without comment. Go away trolls!
- marcosdumay 8y agoJust to post it as an answer instead of a question. That's Haskell's IO. It is just one of the lots of concurrency behaviors available in libraries. Also, parallelism is "free" on pure code.
- _Codemonkeyism 8y agoFrom my understanding, this is not Haskells IO - though my time with Haskell is limited. 1. Haskell uses special notation 'do' to handle access to IO wrapped values, e.g. (contrieved example, one would not use do for such simple cases) y = do x <- xWithIO return x + 1 instead of y = x + 1 2. Haskell method signatures do include IO, e.g. doSomething :: Int -> IO Int instead of def doSomething(i:Int):Int 3. Because IO is usually not the only effect managaging monad, as I've said in another comment, the type signature usually uses a type alias that does alias a monad transformer stack like type Result a = ReaderT Env ( ErrorT String ( StateT Integer Identity)) or concurrency mixed in 4. This the same as my Scala code, where I have cats FutureT monad transformers with Scalactic errors OrT Every stacks showing up all over my APIs as a type alias of 'WithErrors'. 5. 'Also, parallelism is "free" on pure code.' Not sure what's that got to do with it, but yes if you have no concurrency problems (concurrent writes to shared data) you don't need to think about concurrency and parallelism is free. But if my understanding is wrong, I'm happy to learn something about concurrency in Haskell without it showing in code and type signatures.
- jbarham 8y agoSomeone send this article to Armin Ronacher (creator of Flask, Jinja, etc.) so he can understand asyncio since he wrote a much longer and more detailed article explaining why he doesn't understand asyncio (previous discussion at https://news.ycombinator.com/item?id=12829759 https://news.ycombinator.com/item?id=12829759).
- deleted 8y ago[deleted]
- tlrobinson 8y agoThis is essentially how modern JavaScript works, in particular with the addition of async/await syntax [1] (which was originally from C#, I think), but it's been possible with libraries like task.js, co, and Bluebird [2] since generator functions were available (either natively or via transpiling). The main difference is in JavaScript the event loop is automatic and hidden, and asynchronous IO is the default, so it's a bit harder to shoot yourself in the foot. 1. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe... 2. https://github.com/mozilla/task.js https://github.com/mozilla/task.js https://github.com/tj/co https://github.com/tj/co http://bluebirdjs.com/docs/api/promise.coroutine.html http://bluebirdjs.com/docs/api/promise.coroutine.html
- heavenlyblue 8y agoAutomatic and hidden? JS had never provided any proper tools for async debugging. Can I please access a list of all async threads running at this point of time? Surely you can shoot yourself in the foot, but you can also do many other things JS never even attempted to fix. Please stop this JS fanboyism. This is a python thread.
- zbentley 8y agoI don't think it's "fanboyism" to point out one of the most important contemporaries of the Python async/await system, which is also one of the leading platforms that enables that pattern in production today. And yes, you can access a list of async operations (not "threads"; some of them are threads and some of them are multiplexed IO selectors/pollers--know the difference) running at any point in time: https://www.html5rocks.com/en/tutorials/developertools/async-call-stack/ https://www.html5rocks.com/en/tutorials/developertools/async... The asyncio tools in python enable something similar, but very few scripting language debug/tracing tools are as robust as those for JS; that's another area where other languages are often inspired (or aspiring).
- deleted 8y ago[deleted]
- anilakar 8y agoYet another asyncio tutorial that shows you to run a few sleep tasks concurrently. Can we finally get one that shows how to do real stuff such like socket programming, wrapping non-async-compatible libraries and separating cpu-intensive blocking tasks to awaitable threads?
- pmlnr 8y ago...including error handling in async worker loops, please.
- sametmax 8y ago> such like socket programming That's one of my biggest pet peeves (and if you see my other comments, you'll notice I have quite a few). To do socket programming in asyncio, you can either use: - protocols, with a nice reusable API and an interface that clearly tells you where to do what. But you can't use "await". You are back to creating futures and attaching callback like 10 years ago. - streams, where you can use async / await, you but get to write the entire life cycle by yourself all over again. I get that protocols are faster, and match Twisted model, and I get that streams are pure and functional, but none of this is easy. I use Python to make my life easier. If I wanted extreme perfs I'd use C. If I wanted extreme pureness I'd use Haskell. > wrapping non-async-compatible libraries and separating cpu-intensive blocking tasks to awaitable threads That's the one of the things asyncio did right. Executors are incredibly simple to use, robust and well integrated. Problem is: they are badly documented and the API is awkward. I won't write a tutorial in HN, but as a starting point: You can use: loop = asyncio.get_event_loop() future = loop.run_in_executor(executor, callback, arg1, arg2, arg2...) await future If you pass "None" as an executor, it will get the default one, which will run your callback in a thread pool. Very useful for stuff like database calls. But if you want CPU intensive task, you need to create an instance of ProcessPoolExecutor, and pass it to run_in_executor(). I say it's one of the things asyncio did right because the pools not only distribute automatically the callbacks among the workers of the pool (which you can control the number), but you also get a future back which you can await transparently.
- mtrovo 8y ago
- anc84 8y agoThinly veiled advertisement for a "Intelligent Infrastructure Analytics - a Machine Learning driven approach for DevOps & SREs of modern age" company. Seems like hackernoon just published a native advertisement? Not surprisingly shady though, considering the "buy crypto with credit card" link in the top bar.
- shoo 8y agoquoting the article: > Concurrency is like having two threads running on a single core CPU. > Parallelism is like having two threads running simultaneously on different cores > It is important to note that parallelism implies concurrency but not the other way round. Aurgh! I don't think this attempted definition-by-simile is helpful, or even somewhat correct. I much prefer yosefk's way of framing things: > > concurrent (noun): Archaic. a rival or competitor. > > Two lines that do not intersect are called parallel lines. ... > Computation vs event handling > With event handling systems such as vending machines, telephony, web servers and banks, concurrency is inherent to the problem – you must resolve inevitable conflicts between unpredictable requests. Parallelism is a part of the solution - it speeds things up, but the root of the problem is concurrency. > With computational systems such as gift boxes, graphics, computer vision and scientific computing, concurrency is not a part of the problem – you compute an output from inputs known in advance, without any external events. Parallelism is where the problems start – it speeds things up, but it can introduce bugs. ... > concurrency is dealing with inevitable timing-related conflicts, parallelism is avoiding unnecessary conflicts yosefk's whole essay about this is great: https://yosefk.com/blog/parallelism-and-concurrency-need-different-tools.html https://yosefk.com/blog/parallelism-and-concurrency-need-dif...
- foxes 8y agoI also initially thought the same thing. Page two of "Parallel and Concurrent programming in Haskell" maybe says it in a nicer way: >A parallel program is one that uses a multiplicity of computational hardware .... >concurrency is a program-structuring technique in which there are multiple threads of control... (a pdf can readily be found with your favorite search engine for the full extract :) ). I would much prefer to see a precise, rigorous definition and then examples (or eg and then defn is also acceptable), instead of just a list of examples. Examples help you understand a rigorous statement. But, if you only give a hand waving explanation for something, I think it just creates more confusion in the end, as you never know exactly what is correct. It's leaving it open for ambiguity.
- chatmasta 8y agoAlso a big fan of this "Visualizing Concurrency in Go": http://divan.github.io/posts/go_concurrency_visualize/ http://divan.github.io/posts/go_concurrency_visualize/
- kyberias 8y agoIs this any different than in C#?
- jrs95 8y agoIt seems odd that Python ended up with asyncio when they had a clear and successful model they could’ve adopted from Stackless. It would have been more difficult to implement, but it would allow for the same benefits without requiring an asynchronous programming model, which would have reduced the total amount of effort involved in getting to having good concurrency in Python.
- eliasson 8y agoA few years ago I wrote an BitTorrent client in Python 3.5 to get to know asyncio better. Maybe those blogposts are still of use to somebody: - http://markuseliasson.se/article/introduction-to-asyncio/ http://markuseliasson.se/article/introduction-to-asyncio/ - http://markuseliasson.se/article/bittorrent-in-python/ http://markuseliasson.se/article/bittorrent-in-python/
- mikec3010 8y agoMy first endeavor with asyncio felt worse than a beating with a wet rubber hose. But it was a character building experience to really get up close and personal with the asynchronous model and I can definitely see its advantages over imperative, as well as it's always good to have another tool in the box.