4 ms·
What I like about Go is that each co-routine is entirely synchronous, and the only concurrency primitives you're given are channels and mutexes. This makes your
by rcme 4y ago
What I like about Go is that each co-routine is entirely synchronous, and the only concurrency primitives you're given are channels and mutexes. This makes your code so simple and easy to understand.
I just looked up Kotlin's co-routines, and there are a number of annoying hoops you need to jump through:
1. Co-routines can only be built by calling runBlocking or coroutineScope.
2. Functions that are async buy should act like blocking calls in a co-routine need to be marked with `suspend`
3. There are a bunch of primitives you need to use like `launch` or `async`.
4. There are built-in options to make the co-routines lazy, but it makes the API feel sloppy.
5. Rather than locks, Kotlin encourages explicit thread dispatching. Some times you need to do this in Go too, like when using a C library, but in general it's rare.
- ackfoobar 4y ago`launch` is just the `go` keyword. `async` stores the result in a `Deferred` (or `Promise`), which is an abstraction for parallel decomposition. Other than confining UI work to the main thread, I don't think I have seen thread confinement as an alternative to locks.
- rcme 4y agoThat's kind of my point. Go has the `go` keyword. Kotlin has runBlocking, coroutineScope, launch, async, and suspend. Go's simplicity feels nicer to me.
- ackfoobar 4y ago`runBlocking` and `suspend` is the coloured function point. Some hate it, I don't mind it. It has its benefits. --- A quick search on SO gives this example of parallel decomposition. https://stackoverflow.com/questions/57662739/pattern-for-fetching-multiple-fields-in-parallel https://stackoverflow.com/questions/57662739/pattern-for-fet... Starting several `async` tasks, then awaiting them is cleaner for me. But that's subjective. --- Kotlin's structured concurrency handles the context stuff by default. When you handle those concerns (cancellation for example) in go, it's just as complex, but more verbose.
- rcme 4y agoGo's context object is used for cancellation. I think this is pretty simple and has the benefit of avoiding any type of exception handling while still giving you the ability to clean up anything you were doing. In general, if you like Kotlin, I can see why you'd like their approach to concurrency. Kotlin really likes a large standard library with generic blocks that act as syntactic sugar. I used to like that too, but as I've gotten older I've gotten lazier. Kotlin now how too much mental overhead for my old brain.
- tadfisher 4y agoThe coroutines support in the stdlib is here: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/ https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutin... You can build exactly Go's coroutines API on top of these primitives, by encapsulating the Continuation object in Channel and Mutex types, and you would not have to touch the `suspend` keyword (which is sugar for CPS-transforming the function).
- rcme 4y agoI don’t think you can build Go’s coroutine API in Kotlin because Go ensures you can never starve the scheduler. For instance, blocking system calls in Go spawn new OS threads so that they can be pre-empted by other running go routines, and most of the standard API is built with stuff like select, poll, and epoll so you never have to worry about blocking the scheduler. That’s not how Kotlin works, right? So, while you can have a similar syntax in Kotlin, the overall systems will have different semantics.
- tadfisher 4y agoProject Loom will get us that behavior for the JVM, as long as the default dispatcher is modified to support virtual threads. For now, we schedule on an unlimited thread pool using Dispatchers.IO, which is not as cool.
- int_19h 4y agoThe other consequence, however, is that Go coroutines only work with other Go code, and bespoke Go threads and stacks require the runtime to jump through a lot of hoops to do FFI correctly, with the result that the ecosystem tends to rewrite rather than reuse existing code in C and other languages. In contrast, the approach with explicit promises and await can be mapped all the way down to a C struct with a function and a data pointer representing a callback. That is, it is C ABI compatible, and thus it can be easily composed across any boundary that respects that ABI.
- rcme 4y agoThe only runtime hoop I've ever had to jump through was to invoke runtime.LockOSThread() inside a go-routine. In a lot of ways: go func() { runtime.LockOSThread() // ... }() Is simpler than creating a thread in other languages.
- int_19h 4y agoThe runtime jumps through those hoops for you as needed, mostly, but there are consequences to that wrt performance.