Does sound like you want to use channels, and break your logic into small independent parts.
You will have one goroutine using blocking Read() in a loop and feeding data to some channel. When it's done, you write to another channel that exists only for signaling:
defer {
doneChannel <- true
}
for {
data := make([]byte, 65535)
_, err := conn.Read(data)
if err != nil {
errorChannel <- err
break;
}
dataChannel <- data
}
and then in your other goroutine:
for {
select {
case data := <- dataChannel:
// Handle data
case <- doneChannel:
// Other goroutine is done
}
}
The only things shared here are the channels.
The way to avoid too much state and moving parts is to break the problem into isolated, manageable parts that communicate with channels. Often you will have hierarchical relationships like this, where one piece of dumb code exists to pass data from something lower down to somewhere higher up.
It's not hard, although some of the code gets a bit ugly and disjoint at times, especially in how anything synchronous has to use channels and goroutines. For example, today I wrote a simple worker pool implementation that runs a given function in parallel via goroutines and can adjust the number of workers dynamically at runtime. That function has to be declared not just as "func()" but as "func(abortChannel chan bool)", and the worker function has to honour the abort signal when it arrives from the pool. So channels do leak everywhere, even into APIs. (Yeah, I know I can use "chan struct{}" to avoid any storage, but I think "<- true" looks nicer than "<- struct{} {}".)
What is harder is to intelligently handle complex cascading failures. That's what Erlang, with its supervisor tree, is good at. Go's goroutines are "fire and forget" and cannot even be terminated programmatically from elsewhere in the program.
> I know I can use "chan struct{}" to avoid any storage, but I think "<- true" looks nicer than "<- struct{} {}".
I disagree. struct{}{} tells me that the value isn't important. Whenever I use map as a set, rather than a key-value store I use map[string]struct{} (say), rather than map[string]bool. Then I am forced to use the double assignment to check for membership of the set. And that's exactly what I want. I'm able to make my intent more obvious in the code I write. No one will ever look at it and say "but what if it's false?" - I dislike using booleans instead of empty structs in the same way I dislike other C programmers using integers as booleans.
> I dislike using booleans instead of empty structs in the same way
Eh? If you have `map[keyType]bool`, then a key lookup is simply the set membership function. If a key exists, it returns true. Otherwise, false. That certainly doesn't seem analogous to abusing integers as booleans...
Closing the channel is fine, I suppose, although the supervising goroutine now looks a bit odd:
select {
case _, _ := <- doneChannel
// Other goroutine is now done
It's so implicit that you pretty much have to add a comment to the effect of "this will trigger when the channel is closed", whereas the "case <- doneChannel" is so obvious it doesn't need explaining.
Also, I rather prefer the supervising goroutine to "own" the channel, so it should be the one to close it.
> you ca't just have a defer block without a function invocation
Yeah, I was not thinking Go there for a moment. Should have been "defer func() { doneChannel <- true }".
Yeah, the channel approach was rewrite #3 :P It required me to have shared state in a horrible way as well, ruining the whole thing. There really isn't an elegant/functional way to do it, and sharing channels is error-prone itself. I ended up closing the connection to terminate goroutine B from A, and messaging on an already-shared channel to terminate A from B.
Having cascading failures in Go would be fantastic. The problem, unfortunately, is not very amenable to elegant solutions. I might do a writeup at some point.
Does sound like you want to use channels, and break your logic into small independent parts.
You will have one goroutine using blocking Read() in a loop and feeding data to some channel. When it's done, you write to another channel that exists only for signaling:
and then in your other goroutine: The only things shared here are the channels.The way to avoid too much state and moving parts is to break the problem into isolated, manageable parts that communicate with channels. Often you will have hierarchical relationships like this, where one piece of dumb code exists to pass data from something lower down to somewhere higher up.
It's not hard, although some of the code gets a bit ugly and disjoint at times, especially in how anything synchronous has to use channels and goroutines. For example, today I wrote a simple worker pool implementation that runs a given function in parallel via goroutines and can adjust the number of workers dynamically at runtime. That function has to be declared not just as "func()" but as "func(abortChannel chan bool)", and the worker function has to honour the abort signal when it arrives from the pool. So channels do leak everywhere, even into APIs. (Yeah, I know I can use "chan struct{}" to avoid any storage, but I think "<- true" looks nicer than "<- struct{} {}".)
What is harder is to intelligently handle complex cascading failures. That's what Erlang, with its supervisor tree, is good at. Go's goroutines are "fire and forget" and cannot even be terminated programmatically from elsewhere in the program.