6 ms·
Forgive me, but this is such a ball of mud. All of the "easy" introductions into these primitives are basically whitepaper length, with long digressions into th
by jonahbenton 6y ago
Forgive me, but this is such a ball of mud. All of the "easy" introductions into these primitives are basically whitepaper length, with long digressions into the high-level vs the low-level and historical vs modern patterns. And nothing gets into error cases or problems with cooperative scheduling or debugging/troubleshooting or where the GIL applies. In 5 years debugging the rats nest of concurrent python code that people are writing right now will make clear that go really got this right. So sad that python did not.
Edit: elsewhere on HN right now:
https://nullprogram.com/blog/2020/05/24/ https://nullprogram.com/blog/2020/05/24/
- devxpy 6y agoWhy would the Gil apply to asyncio, which is strictly single threaded?
- zomglings 6y agoThe post also discusses concurrent.futures.
- joshuamorton 6y agoWhich is also single (OS) threaded.
- devxpy 6y agoWhy do you say that? I believe concurrent.futures is meant as a way for asyncio to spawn threads or processes -- which lets you essentially call sync code from async and not have it block the event loop. From the docs[1] - The concurrent.futures module provides a high-level interface for asynchronously executing callables. The asynchronous execution can be performed with threads, using ThreadPoolExecutor, or separate processes, using ProcessPoolExecutor. Both implement the same interface, which is defined by the abstract Executor class. [1] https://docs.python.org/3.8/library/concurrent.futures.html https://docs.python.org/3.8/library/concurrent.futures.html
- joshuamorton 6y agoPython's threadpoolexecutor is still single OS-threaded. There will only ever be one thread executing in parallel (due to the GIL). Basically, in python, asyncio and threadpoolexecutor are almost entirely mappable to each other. Neither can accomplish more than the other. Multi-process code can, of course, address some of these issues, but it comes at the cost of spinning up multiple OS processes and costly inter-process communication etc.
- zomglings 6y agoThe only issue I see re: the GIL is that ThreadPoolExecutor (by its name) can mislead them into thinking that they are using proper threads when in fact they are still constrained by the GIL. It's a confusion that has been quickly resolved every time a team mate of mine has run into the issue, but I have seen it happen multiple times (at different places).
- dialamac 6y agoThis doesn’t sound quite right. Unless they changed something threadpoolexecutor does involve multiple OS threads, and is not single threaded. Sure the GIL puts a big limitation on parallel execution, but it is most definitely different. For one, regardless of the GIL it is still possible to have races in multithreaded python code. You have to worry about RMW because the threads are arbitrarily preemptible at a bytecode boundary. This is not the case with asyncio. Additionally you can release the GIL when making calls to C code. Asyncio is giving you concurrency by using nonblocking calls, you can’t run two CPU bound threads in parallel with it. But you can do this with python threads. So it’s not true that neither can accomplish more than the other.
- devxpy 6y agoCan't you just use the ProcessPoolExecutor[1] for CPU heavy stuff? AFAIK, GIL isn't much of an issue with I/O tasks. [1] https://docs.python.org/3.8/library/concurrent.futures.html#processpoolexecutor-example https://docs.python.org/3.8/library/concurrent.futures.html#...
- zomglings 6y agoYou are right, GIL isn't an issue with I/O-bound tasks. The article didn't limit its focus to I/O, though. It felt more like an explanation of modern concurrency and asynchronous options in Python (besides just threading and and multiprocessing). The only practical issue with the GIL in Python is that it forces you to use new (heavy) Python processes to parallelize computationally heavy tasks. Not so much of a concern depending on your application, but it is a gotcha for people new to Python. The real issue, though, as your parent poster mentioned, is the number of different ways to access asynchronicity and concurrency in modern Pythons. It really does run counter to Python PEP20 [0] and can lead to some communication difficulties among developers. [0] https://www.python.org/dev/peps/pep-0020/ https://www.python.org/dev/peps/pep-0020/
- devxpy 6y agoJust an aside from the argument here, I think almost no python API actually follows the zen of python. It's certainly a nice idea, just incredibly hard to actually live upto in the real world.
- sys_64738 6y agoThe first design constraint with so much python development is the battle to stay single threaded. As you point out, debugging concurrent python code is horrendous.
- wegs 6y agoI think this could be done well, single-threaded, but Python reinvented the wheel a half-dozen times now, and I don't think any of the wheels are that great. Having them all in the system at the same time, and without great connections, is what makes this a ball of mud. There's very little glue between them. And none of it is duck typed, like the rest of Python. If I switch between them, I need to rethread the entire system.
- downerending 6y agoTo be fair, debugging all concurrent code is horrendous. If you can use multiple processes instead of multiple threads, you should.
- ricw 6y agoAbsolutely the worst. Ten years ago I encountered a bug in a wireless networking stack that would crash the whole network of nodes. It was in the worst possible location that was highly dependent on timed sending where every nanosecond mattered. This meant i could only use leds to indicate what was going on, as a print or anything the like would screw the timers enough to break the network. Can’t actually remember what caused it in the end. It took me 6 weeks to debug and fix. I did nothing but debugging at the most primitive level. Probably could have done it more efficiently, but I was young and inexperienced. Worst and most boring 6 weeks of my life caused by concurrent debugging.
- brokenmachine 6y agoMust have been really bad if one wireless remote node can crash all the other nodes.
- 6y ago
- HumblyTossed 6y agoI'm a long time fan of Python (and still am), but I completely agree that Go got concurrency right.
- j88439h84 6y agoAgree, asyncio is a mess; Trio gets it right. https://trio.readthedocs.io/ https://trio.readthedocs.io/ It's a much simpler model that's just as powerful, and makes it easy to get things right. It eliminates the concepts of futures, promises, and awaitables. It has only one way to wait for a task: await it. For a theoretical explanation of it's "structured concurrency" model see the now-famous essay https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
- devxpy 6y agoNathaniel has done a great job of drawing parallels between go and goto. I've been stung, time and again by asyncio.create_task() swallowing exceptions[1], and functions not telling callers about background tasks[2]. For others looking to NOT rewrite all their code using a 3rd party event loop, here's a simple reproduction of nursery's behaviour in raw asyncio - Hopefully, this will help lift the curse of asyncio :) [EDIT] A library is also available - https://github.com/Tygs/ayo https://github.com/Tygs/ayo (with amazing operator overload) async def main(): async with Nursery() as ny: ny.start_soon(a_1(), a_2(), ..., a_n()) ny.start_soon(b_1(), b_2(), ..., b_n()) . . . ny.start_soon(x_1(), x_2(), ..., x_n()) class Nursery: def __init__(self): self.tasks = set() def start_soon(self, *coros: typing.Coroutine): for coro in coros: self.tasks.add(asyncio.create_task(coro)) async def __aenter__(self): return self async def __aexit__(self, *args): try: while self.tasks: tasks = self.tasks self.tasks = set() done, pending = await asyncio.wait( tasks, return_when=asyncio.FIRST_COMPLETED ) try: for task in done: await task finally: self.tasks |= pending finally: for task in self.tasks: task.cancel() [1] https://stackoverflow.com/questions/60287285/starlette-asyncio-create-task-doesnt-log-error-if-task-object-is-stored-in https://stackoverflow.com/questions/60287285/starlette-async... [2] https://github.com/encode/starlette/issues/947 https://github.com/encode/starlette/issues/947
- brian_cloutier 6y agoIt is absolutely a ball of mud; asyncio is an embarrassment and what these "easy" introductions never get around to telling you is that it's essentially impossible to write correct asyncio code. Luckily, Trio now exists. Trio has a consistent and simple story for cleanly handling errors and cancellations. Debugging is still a little difficult, but at least code written with Trio has radically fewer problems to debug. Here's a link on the Trio design: https://trio.readthedocs.io/en/stable/design.html https://trio.readthedocs.io/en/stable/design.html There are some more great articles and talks, but I don't have any links at the ready, sorry.
- pgwhalen 6y ago> go really got this right It's also exciting to see that Java has taken the same view, and is well on its way towards delivering it (https://wiki.openjdk.java.net/display/loom https://wiki.openjdk.java.net/display/loom), or even improving on it (https://wiki.openjdk.java.net/display/loom/Structured+Concurrency https://wiki.openjdk.java.net/display/loom/Structured+Concur..., see also: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/#nurseries-a-structured-replacement-for-go-statements https://vorpus.org/blog/notes-on-structured-concurrency-or-g...)