Golang’s most important feature is invisible
blog.devgenius.io
blog.devgenius.io
I also can’t quite grasp the comparison with Java. It’s passing a class instance to the server handler, and that could be doing anything. Nothing there tells you how async or not the server implementation is, and that `os.write()` call might be using a thread pool underneath.
there’s 2 language level syntax features i see that make this better:
one is scalas for comprehension. this lets you write async code that reads like imperative synchronous lines. the other is async/await, which let’s you write synchronous code that behaves like async
As far as I know, Java's new virtual threads have the same idea.
It depends on the contract of the method. If it promises to throw an exception if the call failed, then the current thread of execution can't proceed before the call returns, or it would break the semantics. So even if it uses a thread pool, the current thread will have to block, waiting for some acknowledgement by os.write(), wasting an entire OS thread. As far as I know, Go won't block, the scheduler will just switch to a different goroutine in the meantime. Java's new virtual threads will allow to have mechanism similar to Go's.
Go: fmt.Fprintln(w, "Goodbye, World!")
Java: os.write(response.getBytes());
I didn't read the article but this TL;DR captures the essence of simplicity of programming in Go when writing IO apps. Go essentially optimizes for the majority case: run these things one after another. All the await/async fluff in other languages is evidence of poor/old language design. Every non-trivial production code you see these days in Javascript is littered with await/async as is the case in Python, etc. Why the heck do I have to repeat a keyword 1000s of times when that's expected in majority of languages.
Yes, Go may not cover every case perfectly (e.g. mentioned by sibling re CGO blocking OS thread), but when you look at real world stats of how often this is an actual issue, you find that it's almost noise (i.e. do a code search across Github for instance for such workarounds).
> To get this performance out of Java you would need to add threadpools, futures or some other async library.
What Java needs to be as efficient is the work of Project Loom: Fibers and Continuations. Go also has value types (e.g. arrays and nested structs with inline memory layout) that's the work of Java's Project Valhalla.
Millions is typical of green thread implementations.
There's only one way to write a server in Go and that has pros and cons.
If I have a standalone task, which tires to use 128 cores and manages parallel tasks using goroutines communicating via channels - will I be able to successfully leverage 128 cores.
https://www.techempower.com/benchmarks/#section=data-r20&hw=...
There's a lot of Java above the first occurrence of Go. Also - nobody writes Java web applications using the Sun httpserver. It's verbose and old.
Take for example json: Go's stdlib json implementation is wildly slow. It has to be implemented in reflection, it can't use any code-generation anything, and for backwards compatibility it can never trim any feature, no matter whether it has a performance implication for all users of the package. It's well known to be a slow implementation.
In java, json isn't part of the stdlib. There's jackson if you want one flavor of fast json, jetty ships its own json serializer, etc. This means that if you pick up a new java codebase written in a different framework, it's possible you have to learn the right pattern to json serialize things.
By nature of these third party libraries in java being out of the stdlib, they've been able to grow and become more performant and compete with each other.
Go sits on the opposite side. The stdlib http implementation is a little slow. The stdlib json implementation is incredibly slow. No one wants to build these features outside of the stdlib though, and people would rather every codebase have the same looking go code, even if that means the actual code itself is slower.
What? There are multiple json parsing and http server implementations for Go.
I should have written "no one wants to use third-party versions of these packages", and I don't think that small semantic issue changes my comment meaningfully.
Do you take issue with the way I'm presenting the go ecosystem other than that small pedantic point?
[1]: https://github.com/valyala/fastjson/network/dependents?depen... [2]: https://github.com/elastic/beats
It has 16.8k stars. Is that good?
The two companies I worked in (and still work as of now) both were using fasthttp and non-stdlib json parser (jsoniter and jstream to be exact).
In fact in my experience - nobody uses stdlib implementations outside of projects were stdlib is actually enough (and that's a lot of projects actually) and temp\prototype projects.
No, they don't. But if performance becomes an issue, they can!
> Do you take issue with the way I'm presenting the go ecosystem other than that small pedantic point?
Well, yes, a little. The comparison you're making is presented as "The Java ecosystem has a better outcome than the Go ecosystem". TBH, that's only for performance, and is basically a non-issue if the performance poor.
IOW, you're doing the ultimate in premature optimisation. In the real world, people who run into performance problems will a) run into them sooner with Java, and b) solve them the same way in Go.
Java has a library ecosystem that is stable but not ideological, unlike golang.
I think of golang as the opposite of "The Curse of Lisp" [1], wherein its so standard that it can't fit the evolutionary cracks as well as a less opinionated framework. The ugliness of Java has been its strength. Whether it's Hotspot or ZGC or arch support or whatever, the JVM bytecode and rather limited capabilities of the language forced more focus on the objective quality of the artifacts of the language rather than the language itself. A lot of those artifacts continue becoming more impressive over time.
[1] http://www.winestockwebdesign.com/Essays/Lisp_Curse.html
The fact that callbacks in Go may be on a different thread means it's so damn easy to use a variable reference from an outer scope within in a callback. This increases the chances that someone inadvertently causes a race condition. Something that requires additional awareness and screening during code reviews.
This by itself isn't an indictment of the language as threading is essential to quality GUI applications and as such Go is a great language for writing client applications - which it is largely not used for.
You really need to evaluate the value the overhead Go's thread management brings when used in the context of web servers - where we typically split processes up using container orchestration, making them functionally threaded any way.
Comparatively, TypeScript running on Node or Deno is single threaded yet asynchronous. Using it as a web server means you have a better type system which follows a similar structural typing model to go (interfaces and such) and you don't have to screen for race conditions. In K8 or ECS, you spawn as many processes as you have CPUs and Bob's your unkle.
Personally, I would love it if Go focused on GUI application development but I can _bearally_ write GTK apps with it. Windows and MacOS applications are near-to or impossible to write. Don't even talk to me about Web Assembly. Such a missed opportunity.
And if the there’s one domain where I see an advantage in using inheritance, it’s GUIs.
> Built-in concurrency and a robust standard library
I think that the joy of Go's async-first design really makes sense most to people who have experienced the awful pains of colored async-await in other languages.
I am working on a front end scripting language that does try to make this stuff invisible:
https://hyperscript.org/docs/#async
It's slow as all get out, but it does, for the most part, hide all the async stuff from the script writer so they can write stuff like:
fetch /someurl
put the results into #a-div
wait 2s
transition #a-div's opacity to 0
remove #a-div
And all the async stuff in there (the fetch, the wait, the transition) all happen w/o callbacks, async annotations, etc.How will developers cope with that?
As a side-note, some forms of data races (for perhaps a slightly looser definition of the term) can even happen without explicit asynchronicity, just with callbacks/synchronous events.
The javascript threading model is that there is a single thread executing so within a given bit of code you can be reasonably sure things are safe. The hyperscript runtime only allows one instance of a given event handler to execute at a time (unless otherwise specified) so the primary source of concurrency issues (the same code executing concurrently) is typically not an issue. By structuring your code such that it is on the same element (perhaps triggered by events elsewhere) you can effectively synchronize code in an intuitive manner.
I certainly wouldn't recommend the language for the back end though!
Admittedly, this was back-end, so it's possible that the situation is simpler in the front-end.
Good luck with your project!
Considering that it seems to be a "best practice" to use async functions whenever remotely required, it would be bad developer experience (and lead to unwanted results) to make people do extra work to use it.
And what major issues and downsides this has. One I can identify it would be non obvious on how to do `map` on a Future/Promise, because there is none. So there is no easy way to execute on the green thread code with the result of the remote call, e.g.
let f = asyncFuture()
f.map(|v|: v + 1) // executes also async
let s = await m
println(s)
If f is transparently handled as a future, this is no longer possible, we would have: let s = asyncFuture()
s = s + 1 // not async
println(s)
One would also, to be safe, always use immutable data structure and copy semantics on other classes (e.g. functional lenses and prisms everywhere transparently) - with the performance impact of immutable data structures.It's not an issue with explicit async/await model, because that one desugars into callbacks, which can be described entirely in terms of the already-existing C ABI that is the baseline for FFI in any modern language.
I am not saying that to belittle the author. This is more an observation of how programming seems to have gone from understanding both yourself and what you are doing to shopping bits and pieces and then gluing them together.
Hmm I'm not sure I get this point. Do you mean in comparison with async? Async cross language interfaces should need the same type of continuation mechanisms (eg poll or completion based interop with an event loop) as green threads, no? I think of async as a primarily syntactically explicit construction, while both look similar in ABI, with a virtual stack. If not, I'd love to know why.
For instance, Rust has explicit async/await but the poll method has a pointer to a waker, and a non-standard global/thread local variable is used to communicate with the runtime. Specifically it needs to access the reactor (in Rust lingo).
Either way, this all maps nicely to C FFI. But true green threads don't - since every frame is interruptible by default, all languages involved must use the async stack for all functions, in a way that's compatible with them all. Like tail calls, this is an ABI-level thing that you can't slap on top of the existing C ABI.
It appears as Go doesn't expose green threads as per your definition for it's FFI though, but rather an even simpler synchronous interface in both directions. This makes it impossible to interface with the event loop, and one must use threads. For Go->C, at least, this should not block the runtime since the Go runtime spins up more threads in case of long running synchronous calls (their version of pre-emption).
Go's approach leads to an insular ecosystem that tends to reimplement everything, because that's the only way to get this transparent async throughout.
You can implement async/await-like code that emulates other languages in Go, but it would be a somewhat pointless exercise since you have channels, which offer a much safer and better mechanism for communicating state.
Imo it creates false expectations in programmers, who are surprised, that long-running compute code doesn't get preempted, since the compiler doesn't insert preemption points.
It also robs us of the possibility, of writing event-driven UI applications, where one can make an asynchronous call on the UI thread safely, which will get marshalled back to the UI thread.
https://medium.com/a-journey-with-go/go-asynchronous-preempt...
I guess thats what he was getting at however, not having to do that.
Go’s library is sometimes overly terse, but after having experienced both in production, I much prefer Go’s style.
An example is one of the date parsing functions (I can’t remember details now) which will happily accept an invalid date string (eg 2021-02-35) and silently convert it to a valid date (2021-03-07) despite declaring a parsing Exception.
You see, you only get the exception if you call setLenient(false) or if the date string can’t be coerced. For the life of me, I cannot think of a use case for this behaviour, and it certainly violates the principle of least surprise.
https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-...
https://www.infoworld.com/article/2073649/why-extends-is-evi...
As to workarounds for inheritance in Go, that sounds like trying to write in another language but in Go, which is of course not advisable in any language.
From the article:
> Interface inheritance (the implements relationship) is preferable. You should avoid implementation inheritance whenever possible.
Key words: preferable, and whenever possible. It's sometimes preferable to have inheritance and not possible to leave it out without unneeded extra complexity.
Furthermore, nowadays interfaces in Java (as well as Kotlin, Scala, C# - all of which are much more expressive and have superior modeling capability than golang) can have default implementations, which increases their application even more and reduces the need for explicit inheritance. Not so in golang since interfaces cannot have default implementations there.
> As to workarounds for inheritance in Go, that sounds like trying to write in another language but in Go, which is of course not advisable in any language.
Not quite, it's just where having inheritance would have simplified the design, and improved readability.
Again on interfaces, sometimes less is more. Many people prefer the inverted interfaces of go, declared at point of use. It’s fine to prefer the opposite, but these are deliberate choices, not mistakes or failures.
Re inheritance simplifying a design, it does the opposite in my experience, unless by simplify you mean hide program flow and state.
At the end of the day, golang has classes also whether they care to admit it or not.
But yes, Go is definitely worse in that respect, not better.
Optimistically we may be able to answer op's question sometime in 2024.
The author shows a junior level of understanding which co tradicts "I’ve worked professionally with Go for a couple of years"
This means: not only say "x is good" or "x is bad" without explanation like a little child - but also deliver the source of your knowledge or the proof.
Then your contributions will be seen as mature additions to collective wisdom.
Use the Java Jetty server and Go will hide itself in a corner in shame when benchmarked.
https://www.reddit.com/r/programming/comments/sanusb/comment...
I am happy for Golang users but Gevent is more "synchronous" and easier to write.
Can you give an example to substantiate your post?