3 ms·
> I guess I just have had good experiences with tooling but It doesn't seem that hard to retrofit a threaded language like C++/Python. But why would you want t
by pron 4y ago
> I guess I just have had good experiences with tooling but It doesn't seem that hard to retrofit a threaded language like C++/Python.
But why would you want to if you can make threads lightweight (which, BTW, is not the case for C++)? By adding async/await on top of threads you're getting another incompatible and disjoint world that provides -- at best -- the same abstraction as the one you already have.
- mgraczyk 4y agoI think the async/await debugging experience is easier to understand. For example in the structured concurrency example, it seems like it would require a lot of tooling support to get a readable stack trace for something like this (in python) Code import asyncio async def right(directions): await call_tree(directions) async def left(directions): await call_tree(directions) async def call_tree(directions): if len(directions) == 0: raise Exception("call stack"); if directions[0]: await left(directions[1:]) else: await right(directions[1:]) directions = [0, 1, 0, 0, 1] asyncio.run(call_tree(directions)) Trace Traceback (most recent call last): File "/Users/mgraczyk/tmp/test.py", line 19, in <module> asyncio.run(call_tree(directions)) File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/usr/local/Cellar/python@3.9/3.9.13_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 647, in run_until_complete return future.result() File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree await right(directions[1:]) File "/Users/mgraczyk/tmp/test.py", line 4, in right await call_tree(directions) File "/Users/mgraczyk/tmp/test.py", line 14, in call_tree await left(directions[1:]) File "/Users/mgraczyk/tmp/test.py", line 7, in left await call_tree(directions) File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree await right(directions[1:]) File "/Users/mgraczyk/tmp/test.py", line 4, in right await call_tree(directions) File "/Users/mgraczyk/tmp/test.py", line 16, in call_tree await right(directions[1:]) File "/Users/mgraczyk/tmp/test.py", line 4, in right await call_tree(directions) File "/Users/mgraczyk/tmp/test.py", line 14, in call_tree await left(directions[1:]) File "/Users/mgraczyk/tmp/test.py", line 7, in left await call_tree(directions) File "/Users/mgraczyk/tmp/test.py", line 11, in call_tree raise Exception("call stack"); Exception: call stack
- pron 4y agoNo, the existing tooling will give you such a stack trace already (and you don't need any `async` or `await` boilerplate, and you can even run code written and compiled 25 years ago in a virtual thread). But you do realise that async/await and threads are virtually the same abstraction. What makes you think implementing tooling for one would be harder than for the other?
- mgraczyk 4y agoHow does the tooling know to hide the call to "fork" in the scoped task example?
- pron 4y agoJDK methods can be annotated as "internal" and optionally hidden in stack-traces, but in this case it's unnecessary. The fork call takes place on the parent thread and isn't part of any stack trace when an exception in a child occurs. The regular structuring of exception stack traces takes care of the rest. Remember that Java has been multithreaded since its inception, and virtual threads don't change any of the threading model. They just make Java threads cheap. It's as if they've been there all along.
- mgraczyk 4y agoWouldn't the exception actually come from throwIfFailed? Or does it come from resultNow? How does the tool show it as corresponding to the call to findUser?
- pron 4y agothrowIfFailed throws an exception that wraps one thrown by the child as a "caused by" and lists their stack traces. Java users have had this for many years, but threads were simply costly, so they were shared among tasks. The new thing structured concurrency brings -- in addition to making some best practices easier to follow -- is that the runtime now records parent-child relationships among threads (that now make sense when threads are no longer shared). You can see these relationships and the tree hierarchy for the entire application with a new JSON thread-dump.