Hacker News new | ask | show | jobs
by hackcasual 3849 days ago
Using go for goroutines and channels is a bit like using Perl for regular expressions. The features have been added in a way that makes them easy to use and serves as a nice idiomatic platform, but fundamentally it's functionality other languages can provide via library support.
3 comments

> but fundamentally it's functionality other languages can provide via library support.

Implementing goroutines and channels requires language and runtime support for green threads that are n:m multiplexed on top of native threads. It can not be implemented as a library in most languages, at least not efficiently. Any language with thread support can set up threads and put a concurrent queue between them, but that's hardly the same thing.

Languages such as Go, Erlang and Haskell do this. Interestingly, early versions of Rust had green threads (iirc) but later migrated to using native threads only.

Any language that has continuations, or at least thread-safe coroutines, can implement goroutines and channels. This includes scheme and lua. Also any language where the stack can be directly manipulated can implement those thread-safe coroutines, so that opens up C/C++ and perl and possibly some others.
Yes, all the languages you mention have the necessary language and runtime support required, probably a handful of others too (but by no means every language out there).

C and C++ are a bit different because you need to resort to assembly and know details about the target arch to do stack switching but that's acceptable.

It's been done in C; check out libmill[0], which even matches the syntax pretty well.

[0]: http://libmill.org/

If "it" includes parallelism then no, libmill has not done it:

"Libmill is intended for writing single-threaded applications." http://libmill.org/documentation.html#multiprocessing

Yeah, you can do this in C if you do stack switching with a little bit of assembly. It's kind of doing a custom runtime environment for C. Not many other languages can do this.
No ? Qt, Gtk, ... all do it.
Huh? Care to elaborate on this? As far as I know, GTK (and Qt IIRC) use a single threaded event loop. That's not at all the same thing (albeit can be used for similar things).
Qt and Gtk's single threaded event loop are akin to what Go calls it's scheduler. That scheduler in Go is also (partially) single threaded, but event handlers run in other threads.

Having event handlers run in separate threads is very much supported in both Qt and Gtk (they can't be UI event handlers in quite a few cases, but network events and file reading in separate threads scheduled by the central event loop like in Go is not a problem).

I will say that it's much better organised and with much less caveats in Go.

And a point of personal frustration : both Qt and Gtk support promises through the event loop, Go does not. I find that a much more natural way to work with threads.

Go scheduler is not single threaded (whatever that means).
Go's scheduler is a single threaded event loop, like every other scheduler on the planet. It runs, sequentially, in different threads which makes the situation confusing, but it's still single threaded.

It's also cooperative. An infinite loop will effectively kill it (a single infinite loop will kill it before Go 1.2 I believe, but now you need enough of them). More importantly, there's a number of simultaneous syscalls that will kill a go program.

I like about go that it's moving the OS into the application. The thing is, Go's OS is not a very good one. It doesn't have the basic isolation that OSes provide. I hope it will improve.

+1 on this. Clojure's core.async[0] is the perfect example of an implementation of CSP as a library.

Even JS can be used to implement such concepts via the use of generators[1].

[0] https://github.com/clojure/core.async

[1] https://github.com/ubolonton/js-csp

I used to feel this way until I realized the limitations of core.async. In Go I don't have to worry about whether the particular functions I'm calling, especially IO-related, are blocking or not, as Go will create new lightweight goroutines as necessary to deal with all that. With core.async, if I use blocking IO inside of a coroutine I risk causing thread starvation. See http://martintrojer.github.io/clojure/2013/07/07/coreasync-a...

Maybe things have changed since 2013, but I feel like this is a fundamental limitation of running on the JVM vs what Go can provide in its runtime.

Edit: Also, it appears to be much easier to simply "run out" of Clojure coroutines than Go goroutines, but perhaps that's also changed. Anyways, my point is that by core.async operating as a macro you still can't overcome limitations of the underlying runtime, whereas Go's runtime was purposely-built to support goroutines.

Small remark: Go I/O layer is safe to use only with network I/O. Only network I/O plays nice with goroutines.

File I/O or everything else treated as syscall by Go runtime might turn you program into 10k-os-threads-monster. Scheduler will be creating new OS threads to replace those locked on syscalls until thread limit is reached and whole program crashes. Only way to prevent it is to restrict your syscall layer into fixed-size goroutine pool.

I had an interesting case recently - my app serves some data from tons of files laying in NAS, accessing it by NFS mount and one day NAS hunged completely, every I/O call to it was lasting forever. Even 'ls /mount-point-of-nas' was just doing nothing forever until Ctrl-C. In my case I've applied poweroff-poweron cycle to NAS, and everything went right in minutes, just as NAS booted. And after it I wondered, what if my server was written in Go, instead of Erlang...

And, BTW, you can never be sure, that underlying libraries of your code are safe to use.

Correct me if I'm wrong, but describing core.async as "a library" isn't perfect in the context of a golang discussion. Doesn't the `go` macro rewrite the abstract syntax tree / JVM bytecode to make e.g. the `!<` macro co-operate with the channel?

https://github.com/clojure/core.async/blob/master/src/main/c...

That's not something that could be done with golang as far as I know.

I'm not sure to understand your point. Could your clarify?
`hackcasual` was saying that certain language features can be added as a library rather than needing to be integrated into the core language

> "fundamentally it's functionality other languages can provide via library support"

You were saying that CSP can be added as a library, citing Clojure's core.async.

All I was saying was that the way in which core.async was implemented doesn't feel like a great example of a 'library' in the sense that most people would understand in the context of a discussion about Golang.

Golang is a static, compiled-to-machine-code language without macros (in the LISP or C sense) or homoiconicity. The reason core.async can be implemented as a library in Clojure is that it has these things.

If you're talking about adding CSP to a language just by adding a library and without having to get into the internals of the language, core.async isn't a good example.

Again, happy to be corrected.

Well core.async is a Clojure library so it uses features available in Clojure. I don't see how it would affect the fact that it is a library.

I've also linked to js-csp, a JS library obviously not implemented using macros.

I can also find other examples of implementation as libraries, but I have no experience with them:

- Scala: https://github.com/rssh/scala-gopher

- F#: https://github.com/Hopac/Hopac

- C++: http://www.cs.kent.ac.uk/projects/ofa/c++csp/

Wasn't saying it can't be done! Just nit-picking at the particular example wrt to golang.
It's also a perfect example of the limitations of that approach - core.async had to make serious compromises in its interface because it was 'just a library': expressions with <!'s and other calls can't just be pulled into functions or for-comprehensions like normal code. That's not to say it's poorly-done, or not useful - it is well done and useful, and those compromises are in line with clojure's goal of integrating well with host vms. It's just an example of how builtins can be "simpler" sometimes. https://github.com/clojure/core.async/wiki/Go-Block-Best-Pra...
> +1 on this. Clojure's core.async[0] is the perfect example of an implementation of CSP as a library.

With the added convenience that shared mutability is pretty much nonexistent.

I tried making a CSP library for C. It was not very pleasant. At best you end up with something slightly significantly less safe than POSIX threads, but now with message passing. While C is an extreme example it's certainly not true that all languages can add CSP/actors/whatever in an appetizing form through a library.

Anyway, there's a reason the phrase "tacked-on" has such negative connotations.

What's your take on libmill?