Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

`Executor.newVirtualThreadPerTaskExecutor` versus `go` really gets to the heart of why I think that Go developers aren't going to be switching.

edit: Sorry, it's actually:

    try (var executor = 
        Executors.newVirtualThreadPerTaskExecutor()) {
       executor.submit(...)
    )
instead of `go`


You’re likely to need a lot of boilerplate that “go” statement won’t generate: pass and wait for completed results, report errors, timeouts, cancellation, bounded parallelism, pushback, service monitoring.


My comment is perhaps too glib. I'm not trying to say that Go is better, I frankly like Java more. I only mean that if I'm in a Go mindset and I read the linked document on virtual threads, and I see that code example, I'm going to close the tab.


Starting a virtual thread in Java isn't that clumsy:

   Thread.ofVirtual().start(() -> System.out.println("Hello"))


The go example is missing the channels, select, wait group, context, cancellation

Doesn’t sound like a fair comparison thou the Java version is missing things


> The go example is missing the channels, select, wait group, context, cancellation

The Java version would require all of those in the exact same way though.


Java has structured concurrency in the works, which will not be much more verbose, unlike golang.


If you want to use that with more concise syntax, there is Kotlin for that. In which case abstracting that is one line of code tucked away in a utility file:

    fun <T> go(body: ExecutorService.() -> T): T =
        Executors
            .newVirtualThreadPerTaskExecutor()
            .use(body)
Now you can write:

    val result = go {
       val result1 = submit { slowOp() }
       val result2 = submit { blockingOp() }
       result1.get() + result2.get()
    }
or words to that effect. You can also reduce it a lot in Java too, as mentioned by another commenter.

    class Shortcuts {
        static <R> R go(Function<ExecutorService, R> task) throws RuntimeException {
            try (....) { return task.apply(service) }
        }
    }
and then you can write:

    var result = go(service -> {
        var result1 = service.submit(() -> slowOp());
        var result2 = service.submit(() -> blockingOp());
        result1.get() + result2.get();
    });
using static imports.

Now if you're making a cultural point then sure, Java APIs tend to be named quite explicitly. It has advantages when searching or auto-completing, and it's easy to wrap with aliases if you want to abstract a boilerplate pattern away.


Wrap it in class Go { static void go(…) } and it will look better with “import static”


I assume `Go` would have to be Closeable and you'd still need the try-with-resources, right?


You don't need a try-with-resources, you can manually call close if you want, or has to work in a non-lexical scope.

But you often want the concurrently running threads to return some results, that try-with-resources is part of the structured concurrency that helps you with closing off a branching point, similarly to how we use while loops instead of gotos. `go` in itself corresponds to `goto` basically, with many of the same negatives.


Maybe you re-read my comment, check the signature of Go::go() and think again?


So basically executor.submit(). And you still have a language that is significantly less verbose than go, objectively.


Yes, if you ignore more than half of the code it's quite concise.


How many thread pools do you want to use? You only need that once.


go will always lose vs java when it comes to code verbosity


Extend that to a full function and the Go code will be 2x more verbose than Java with all the `if err != nil` stuff.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: