Hacker News new | past | comments | ask | show | jobs | submit login

I'm not sure this is exactly what you're asking, but returning from a goroutine ends it. The rest is just garbage collected as needed.

If you choose to select indefinitely, yes, you will need another channel to signal when you should return, or the goroutine will continue to run, and continue to hold a reference to that first channel.




It doesn't even require "selecting indefinitely." Every time you create a goroutine you need to worry about how it terminates, or you get a memory leak. If it uses channels, you need to make damn sure you perform all the proper operations on it, consume all the values from it eventually, etc. It's like manual memory management, except that the invariants are all implicit and poorly specified, like "Take at least 10 values from this channel, and I will terminate" or "Send me a value on this other channel over here and I will terminate". Sounds like a nightmare to reason about in some cases, which is a shame because Go does so much else Just Right^TM

A concrete example, slide 34 [1] contains this code:

    func main() {
        c := boring("Joe")
        for {
            select {
            case s := <-c:
                fmt.Println(s)
            case <-time.After(1 * time.Second):
                fmt.Println("You're too slow.")
                return
            }
        }
    }
This is code that looks really simple, but it's only correct because the process is known to terminate. You couldn't, for example, have the body of this function as a subroutine somewhere: every time you execute it a boring() goroutine is going to be left hanging, which is a classic memory leak. What's worse, "c" looks like an average variable I don't need to worry about (yay garbage collection!) but it's not because it escapes to a running goroutine.

Correct me if I'm wrong but even if boring() was a much simpler function that returned after generating one value, if the timeout happened first it would be left hanging. In that case the only thing that would make the program correct is that of two racing goroutines, one is expected to return first. That's the kind of thing that keeps me up at night, especially when Go makes it so easy to treat goroutines and channels like every other GC-managed data structure when in fact they are every bit as leaky and difficult as threads.

[1]: http://talks.golang.org/2012/concurrency.slide#34


Correct me if I'm wrong but even if boring() was a much simpler function that returned after generating one value, if the timeout happened first it would be left hanging.

Such is the case of the time.After() function, which easily solves the problem by returning a buffered channel. That would not work for an infinite generator of course.


This is true. You can reason about a lot of particular cases and design a correct program based on these behavioral invariants. The trouble is they tend to end up being these pervasive, whole-program invariants. For example, if you buffer exactly 1 value like this, then you need to make sure the channel only escapes as a read-only channel.

The power and orthogonality of Go's concurrency primitives is actually encouraging. I'm not picking holes in it because I think Go is a bad language. On the contrary, I just think that it's not the endgame of concurrent programming. Using channels is not a safe operation in the same way that passing around memory references in a GC-managed language is safe, or passing values in a type-safe language is safe. You can write a correct, concurrent program with pretty much any behavior you want in Go, but it's still a minefield of deadlocks and memory leaks. I think there's room for improvement there.


I'm learning Go to evaluate whether I want to try to use it at work. After using Erlang, Python's gevent, and Haskell, each quite substantially for concurrency (though to varying degrees, Erlang the most), I'm finding Go's concurrency constructs to be by far the most dangerous of the four. It's not even close. Go is the only one with synchronization on the message sending, and I'm trying to keep an open mind on the utility of that, but so far it mostly seems to have the effect of turning valid code in any other system into a deadlock in Go, for the purpose of avoiding putative problems in asynchronous sending that just don't seem to happen in practice in the other environments.

(By open mind, I mean that I acknowledge that I have years of being steeped in an asynchronous message passing environment and that I may need to unlearn some things and learn others. I'm a polyglot programmer, so this isn't my first time for that, either. There are some some useful things that Go's channel style is easier to work with than Erlang's style. Still, right now I'm cautiously negative on the net utility of that, even so.)

I've deadlocked Erlang twice in many years. It didn't make it to production either time. I deadlocked Go twice in the first two days, and several times since. (And Go terminates the entire process upon deadlock.) And what really concerns me in terms of recommending it for work is that while I instantly understood how I deadlocked Go, it was only because of the fact that I'm fairly experienced now in reasoning with these sorts of advanced concurrency constructs. Many of my coworkers would have been stuck for hours.

It's not out of the running yet, considering many of its positive qualities and the competition it is up against, but the Unambiguous Win I was hoping for is not emerging. (Many of these issues may be avoidable with style; for instance, one can write goroutines that manage resources in a fairly Erlang-y style and which are services that have a continuous receive-reply loop, and those are safe enough. But I'd really rather have more compiler support here rather than depending on convention; that's how we got to the realization that conventional threads are a bad idea in the first place.)


> I'm finding Go's concurrency constructs to be by far the most dangerous of the four.

That is exactly what I can't get over. I don't understand why this is the default. Most processes in the real world are not synchronous. Sending an asynchronous message makes more sense as a primitive (synchronous can just be modeled on top as a mini protocol, sender sends his own address). The other way is not as clear. (Spawn a separate concurrency primitive and send to the channel from there?).

The other thing that bothers me is the focus on channels rather than on processes. The two can be seen as equivalent but for some reason process-centric design seems more natural to me.

There is of course Scala with Akka but I don't see JVM a benefit, I see it as a nuisance (it is more of an irrational political thing probably come to think of it).


Though I'm sure you will, I would say give it some more time.

My start with go was just like yours, but it quickly started to "click", and more robust patterns began emerging more naturally for me. I don't think I can verbalize yet what has made the difference for me, but it may something like "structure your code around channels" vs "use channels to synchronize your code".


Yes, I'm not done. I am mostly just a bit disappointed that it still remains relatively easy to get into trouble with concurrency. It takes much more work to get in trouble with those other environments. It is a positive that it does scream bloody murder, rather than silently corrupting things or the other ancient failures of shared-state threading.


+1 for this!

When I first started with golang I too was flailing around with deadlocks, but once you have spent a bit of time with it, it becomes obvious when you have messed up.

I think that this is more of a lack of familiarity with the language rather than an intrinsic problem with golang.


This is extremely common and is like second nature to any Go programmer. This is true of any sort of concurrency.. If you have a for-loop in a Java thread, you're going to have some sort of terminating condition or way of signaling death to the thread.




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

Search: