Go is garbage-collected, with a good concurrent garbage collector, so all this is memory safe. Mostly. Maps (Go's dictionary type) aren't concurrency-safe for performance reasons, and there's an known exploit involving slice descriptors.
Go is garbage-collected, with a good concurrent garbage collector, so all this is memory safe. Mostly. Maps (Go's dictionary type) aren't concurrency-safe for performance reasons, and there's an known exploit involving slice descriptors.
One is the ownership model. You can close a channel, and sends will then panic (there's no way to check if a channel is open), so ownership is always in the hands of the code that writes to the channel. You can -- and must, if you ever want multiple producers with a single channel -- abstract ownership into a wrapper that keeps the channel and a tracks whether the channel has been closed, but it's tedious to implement it over and over (it needs to be thread safe, too).
It's tempting to use channels as part of a public interface (between two cleanly separated packages), but this nearly always is a bad idea. Channels just don't compose well as an iteration construct, and you encounter ugly surprises such as realizing the caller is using an unbuffered channel causing stuff to block. It's almost always better to offer a callback interface (push) or an iterator interface (pull), and then either side can use channels internally, or not at all, as needed.
In practice, it's sometimes surprisingly hard to avoid goroutine leakage with channels. It's a simple mechanism that gets complicated fast, especially as you combine them with mutexes and error handling (though ErrorGroup is a timesaver that ought to be part of the standard library).
And finally, the behaviour of nil channels (they block forever!) is unfortunate. It is touted as a feature, but for me it has been exclusively the source of surprising bugs.
To be exact: you cannot "non-destructively" check if a channel is closed. I.e.: the way to check is by using a select statement to do a "soft write", I.e. "try to send this value but don't panic if it fails." Same for reading. This means you can't check the state of a channel without modifying it if it isn't closed.
Graceful shutdown of systems with lots of asynchronous communication is hard, but this makes it harder than it has to be.
*sigh, after all that time I still make this mistake. Goes to show how often I actually use channels. :/
I use queues of my own implementation when I really need performance. But then I have lots of experience with that, and I have the implementation of maybe the world's highest throughput SPSC queue, outperforming the Lynx queue that was featured here on HN earlier in the year. I'm still trying to make time to write that up properly as a blog post (I hate writing blog posts.)
The language maintainers have not been very receptive and the broader community has at times been outright hostile.
To me the tricky part seems to be how they need an implementation good for all cases. Performance engineering is all about tradeoffs. Memory usage, throughput, latency are often competing requirements and you have to choose one. Furthermore, unless they can somehow statically determine how many threads access a channel, they have to use a MPMC queue, which is a performance anti-pattern.
I would modify the language (maybe just make() builtin) to allow specifying what type of Channel is needed where, SPSC, MPSC, SPMC, etc. But we all know how that would be received.
There was also this article about some of the problems with Go channels: http://www.jtolds.com/writing/2016/03/go-channels-are-bad-an...
[1] http://blog.stalkr.net/2015/04/golang-data-races-to-break-me...
Can you cite a source for this, or is this your personal experience?
Rust has very thorough ownership semantics baked into the language to avoid such race conditions. But anyway, the idea of goroutines is that they should primarily be used to transfer values, which results in both goroutine's having their own separate copy of the data, and thus no races.