5 ms·
> No more colored functions [1] That's a property of the function, whether or not you want the type system to help you with it. I can call a function myRedFun
by mrkeen 6y ago
> No more colored functions [1]
That's a property of the function, whether or not you want the type system to help you with it. I can call a function myRedFunction, but if it calls a blue function, it's blue.
> or worse Completable<Future>
What is this?
Future<Result> f = e.submit(() -> { ... return result; }); // spawns a new virtual thread
> This model of concurrency is so much better than the async/await mode
ThreadFactory tf = Thread.builder().virtual().factory();
ExecutorService e = Executors.newUnboundedExecutor(tf);
Future<Result> f = e.submit(() -> { ... return result; }); // spawns a new virtual thread
...
Result y = f.get(); // joins the virtual thread
I don't get it. This just looks like async/await (submit/get) with extra steps.
Why not just write
Future<Result> f = async ( ... result; )
Result y = await (f);
- pron 6y ago> That's a property of the function, whether or not you want the type system to help you with it. The problem is that in, say, C# or Kotlin, subroutines with the same semantics come in two different colours. The type system "helps" you distinguish between things that aren't meaningfully distinguishable. > I don't get it. This just looks like async/await (submit/get) with extra steps. You only need to submit if you want to do stuff in parallel and then join (also, there are no extra steps). The analogue to: var a = await doA(); var b = await doB(); is just: var a = doA(); var b = doB();
- h-cobordism 6y ago(1) That sounds like a recipe to unintentionally miss a ton of concurrency. Having to put an `await` there is a great indicator that you're forcing an order of execution. (2) Can you give an example of an async and a non-async subroutine that have "the same semantics"?
- pron 6y ago1. There is no need for await. Threads imply sequential execution. 2. sleep vs. delay in either C# or Kotlin.
- nlawalker 6y ago> 1. There is no need for await. Threads imply sequential execution. How do you execute doA() and doB() concurrently? In C# you can do something like: var t1 = doA(); var t2 = doB(); await Task.WhenAll(new[] {t1, t2}); var a = t1.Result; var b = t2.Result; EDIT: Ah, never mind, I just saw "You only need to submit if you want to do stuff in parallel and then join" above.
- jerf 6y agoThe reality is that you rarely want to doA and doB concurrently, so optimizing syntax for that case is not useful, whereas you want to be able to call functions without having to worry about their color all the time, where "all the time" here is typically >1 time per function. Many of you are perhaps scratching your head and going "What? But of course I concurrently do multiple things all the time!" But this is one of those cases where you grossly overestimate the frequency of exceptions precisely because they are exceptions, and so they stick out in your mind [1]. If you go check your code, I guarantee that either A: you are working in a rare and very stereotypical case not common to most code or B: you have huge swathes of promise code that just chains a whole bunch of "then"s together, or you await virtually every promise immediately, or whatever the equivalent is in your particular environment. You most assuredly are not doing something fancy with promises more than one time per function on average. This connects with academic work that has showed that in real code, there is typically much less "implicit parallelism" in our programs than people intuitively think. (Including me, even after reading such work.) Even if you write a system to automatically go into your code and systematically finds all the places you accidentally specified "doA" and "doB" as sequential when they could have been parallel, it turns out you don't actually get much. [1]: I have found this is a common issue in a lot of programmer architecture astronaut work; optimizing not for the truly most common case, but for the case that sticks out most in your mind, which is often very much not the most common case at all, because the common case rapidly ceases to be memorable. I've done my fair share of pet projects like that.
- tjalfi 6y agoParaSail[0] is a parallel language that is being developed by Ada Core Technologies. It evaluates statements and expressions in parallel subject to data dependencies. The paper ParaSail: A Pointer-Free Pervasively-Parallel Language for Irregular Computations[1] contains the following excerpt. "This LLVM-targeted compiler back end was written by a summer intern who had not programmed in a parallel programing language before. Nevertheless, as can be seen from the table, executing this ParaSail program using multiple threads, while it did incur CPU scheduling overhead, more than made up for this overhead thanks to the parallelism “naturally” available in the program, producing a two times speed-up when going from single-threaded single core to hyper-threaded dual core." One anecdote proves nothing but I'm cautiously optimistic that newer languages will make it much easier to write parallel programs. [0] http://www.parasail-lang.org/ http://www.parasail-lang.org/ [1] https://programming-journal.org/2019/3/7/ https://programming-journal.org/2019/3/7/
- lmm 6y ago> The problem is that in, say, C# or Kotlin, subroutines with the same semantics come in two different colours. The type system "helps" you distinguish between things that aren't meaningfully distinguishable. The distinction is important though, because it affects the observable behaviour of other effects present in these functions. https://glyph.twistedmatrix.com/2014/02/unyielding.html https://glyph.twistedmatrix.com/2014/02/unyielding.html The only cases where you want the same function to come in two colour variants is when it's a higher-order function, and in that case what you really want is not to ignore colour but for the function to be polymorphic over colour. (i.e. higher-kinded types).
- pron 6y ago> The distinction is important though, because it affects the observable behaviour of other effects present in these functions. Only if the language is single-threaded to begin with, like JavaScript.[1] There is no real difference in C# and Kotlin between, say, sleep and delay. It's a syntactic distinction between semantically equivalent terms (for all practical purposes). [1]: Even then there are better ways to reason about effects.
- lmm 6y agoHmm. I find it hard to believe that whether a given block of code executes on a single OS-level thread or yields and is rescheduled across multiple OS-level threads is never semantically important (to resort to an extreme example, one kind of function can be used in a callback that's invoked by native code and the other can't) - particularly given how many languages (including Java!) started with a green threading model only to later abandon it. And so I'm very dubious about erasing that distinction in low level functions, even if it's not relevant in the vast majority of cases, because once you erase it it's impossible to ever recover it at higher level.
- pron 6y agoIt is never semantically important (in a context that is multithreaded anyway) because you don't have more control over scheduling with async/await than you do with Loom's virtual threads. In either case, the code doesn't know if scheduling takes place on a single kernel thread or multiple ones. Nor does it know about the existence of other threads running concurrently. There could be some technical differences, like various GPU drivers allowing only specific kernel threads to use it, but Loom answers that with pluggable schedulers. The difference in the native call case isn't semantic; it's, at worst, a difference in performance [1], and as far as I know, no one is making that distinction today, so there's nothing to erase. Moreover, as Java doesn't have async/await, there isn't even an artificial distinction to erase. I don't know all the reasons Java abandoned green threads. One of them was that the classic VM that had green threads was simply replaced with HotSpot; it wasn't an evolution of a single VM. But more practically, and putting aside the fact that it was M:1, at the time there Java code relied on native FFI a great deal, whereas today it is very rare in general. [1]: This is a little inaccurate. There could be a difference in liveness properties (deadlock) in that case, but neither async/await nor kernel threads, for that matter, make intrinsic liveness guarantees. You have to trust your scheduler in all cases.
- mrkeen 6y ago> distinguish between things that aren't meaningfully distinguishable. Are these semantically the same? Runnable action1 = () -> System.out.println("foo"); Runnable action2 = () -> System.out.println("bar"); action1.run(); action2.run(); var action3 = runAsync(() -> System.out.println("foo")); var action4 = runAsync(() -> System.out.println("bar")); action3.join(); action4.join(); > You only need to submit if you want to do stuff in parallel and then join (also, there are no extra steps). I'm not comparing parallel to sequential. My point was that we're already doing parallel programming: CompletableFuture<Result> fx = CompletableFuture.supplyAsync(() -> result); CompletableFuture<Result> fy = CompletableFuture.supplyAsync(() -> result); Result x = fx.join(); Result y = fy.join(); Parent doesn't like the existing parallel model. New model looks like: // > plus no need for thread pools anymore. ThreadFactory tf = Thread.builder().virtual().factory(); ExecutorService e = Executors.newUnboundedExecutor(tf); // > No more [...] Completable<Future>, promises et al. Future<Result> fx = e.submit(() -> { ... return result; }); Future<Result> fy = e.submit(() -> { ... return result; }); Result x = fx.get(); Result y = fy.get(); If going from the first to the second is an improvement, I don't understand the excitement. Especially when other languages look like: fx <- async (return 3) fy <- async (return 3) x <- wait fx y <- wait fy
- pron 6y ago> Are these semantically the same? No, but that's not what I meant. These are semantically the same: action1sync(); action2sync(); await action1async(); await action2async(); > If going from the first to the second is an improvement, I don't understand the excitement. The excitement is about going from my second example to the first. > Especially when other languages look like: It doesn't matter what they look like in the code. They suffer from all the problems I mention in the article: exceptions lose context, debuggers and profilers lose their effectiveness, APIs are split into two disjoint worlds. Virtual threads gives you code that doesn't just look synchronous, but behaves like that in every observable way. What the code looks like on the screen is a very small portion of the problem we're trying to solve.
- Skinney 6y agoWell, the first two lines are usually written somewhere at the top level. You won't actually write that anytime you want something to be async. The last two lines are what you usually write, so most of the time, the number of lines of code are equivalent. Second, Java doesn't have async/await today. If Java was to introduce it, it wouldn't be compatible with the code written today. The big benefit of project loom is that code written today will get all the benefits of async/await without changing any code. In fact, because it's not important to pool threads, it actually gets easier. The big problem with async/await in languages like C# or even coroutines in Kotlin is that it doesn't mix with the "old" api. The get the benefits you need to do a huge refactor of your code, and you need to make sure that any library you pull in is compatible. Java's approach seems much better.
- jayd16 6y agoYou don't. You can call synchronous functions from async functions just fine and vice versa. The syntax isn't identical but I don't understand this split the world meme.
- toast0 6y agoIn my experience with C#, I ran into apis I wanted to call that were async. I couldn't call them, even as Result foo = await async_thing(), unless my function was marked as async. I don't remember having a problem calling sync functions from async functions though.
- uryga 6y agoafaik sync can call async if the language allows you to make an executor/event-loop. e.g. with python's asyncio it'd be something like def sync_foo(): task = async_bar() loop = asyncio.new_event_loop() return loop.run_until_complete(task) after all, unless the loop is built into the language (like JS), you need bootstrap the async code somehow
- jayd16 6y agoYou can just use: task.RunSynchronously() mildly annoying but far from impossibly split.
- eggsnbacon1 6y ago>I don't get it. This just looks like async/await (submit/get) with extra steps. Why not just write because altering the underlying thread pool can convert all existing code to fibers without syntax changes. This is the crux of "colored functions", two different signatures for async/non-async. The implications for improving the performance of existing code are enormous. Most Java API's give you at least indirect control of the thread pool used. Which means they can be converted to fibers without updating the library. In Java, executors are basically thread pools. If you can update the executor to use fibers the rest of the code is using fibers now. Your second example only converts a single call site to fibers. A thread pool could be used in hundreds of places. In practice, this is a huge difference. I can update my Streams to use a fiber pool, and all across my app the hundreds/thousands of streams are using fibers now. Same story with REST and DB access. There's usually a single thread pool for each. A few lines of code to update the pools and my REST and DB calls are all using fibers.