I've started looking more heavily into Go, and one of the things that surprised me is the semantics of channels. In particular, there seems to be a lot of confusion in the community about how to signal closure of a channel under various usage scenarios.
Try it :).
On Wed, Jul 25, 2012 at 7:37 PM, Marcelo Cantos wrote:I've started looking more heavily into Go, and one of the things that surprised me is the semantics of channels. In particular, there seems to be a lot of confusion in the community about how to signal closure of a channel under various usage scenarios.I wouldn't say there is a lot of confusion. There are people who insist on doing it incorrectly for the sake of brevity and there are people who write code that depends on the behavior of the current implementations, but it's exceedingly simple. A channel close operation is a send operation after which no other messages can be sent.
If a sender dies without knowing it, and thus unable to send a close, is this also reflected to the receiver? How?Alternatively: If the sender does not know that it has died, would the run-time system know it - and send the close on behalf of the goroutine (like closing all open files)?
The Emperor's Old ClothesCommunications of the ACM, Feb. 1981Hus Turing Award lecture,"One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies."(About ADA at the stage it was:)"Gradually these objectives have been sacrificed in favor of power, supposedly achieved by a plethora of features and notational conventions, many of them unnecessary and some of them, like exception handling, even dangerous."
There are a bunch of examples of this in go-nuts and other places(talks, std lib, blogs etc.) under request response chan.
This is not too hard to multiplex in a number of ways, for example a load balanced approach where new requests are routed to the first non-busy producer via a select.
close() cleans everything up nicely in a cascaded fashion, where the multiplexer detects the close and closes the request chans to the producers. This has the added benefit of not interrupting the in-flight requests (if you still care about them). I'll post some code examples later today.
@Marcello (sorry, can't quote ATM)
You can flip the idiom around, turning the producers into consumers and vice versa. Have the data generators hold the receive end of a channel of requests, where each request contains a chan to respond with data on (commonly referred to as the request-response chan idiom, or something).
When the data receiver closes the request chan the data generator will detect it via the val,ok := <- chan idiom.There are a bunch of examples of this in go-nuts and other places(talks, std lib, blogs etc.) under request response chan.
This is not too hard to multiplex in a number of ways, for example a load balanced approach where new requests are routed to the first non-busy producer via a select.
close() cleans everything up nicely in a cascaded fashion, where the multiplexer detects the close and closes the request chans to the producers. This has the added benefit of not interrupting the in-flight requests (if you still care about them). I'll post some code examples later today.
How about this ?
http://play.golang.org/p/PSpnGj632G
Uriel
Sorry, but I don't quite understand what "endrange()" means or how thinking this way solves the problems that are bothering me. Say I have five producers that don't know about each other, all writing into a single channel read by a filter, which passes transformed/filtered messages through to a downstream consumer. How do the five producers let the filter know when it (and, transitively, the downstream consumer) is not needed any more? What is the idiomatic solution for this very common scenario? Is it a separate control channel? Is it a channel per producer, which dramatically complicates the filter and still requires a control channel? Is it a HELLO-MSG1-MSG2-...-GOODBYE protocol? Just let the filter leak? None of the solutions I can think of are particularly palatable. So, what am I missing?
We could also introduce a statement "ok
= c <- v" that returns an indication of whether the value was sent,
and similarly a "case ok = c < -v:" in a select statement.
// Currentfor i := 0; ; i++ {select {case c <- fmt.Sprintf("%s %d", msg, i):// Do nothingcase <-quit:fmt.Println("Cleaning up...")quit <- true}}// Send returns bool
for i := 0; c <- fmt.Sprintf("%s %d", msg, i); i++ {}fmt.Println("Cleaning up...")done <- true
if that is true won't people be able to do stuff like this:if c <- fmt.Sprintln("Hi") || c <- fmt.Sprintln("Hi") || c <- fmt.Sprintln("Hi") {fmt.Print("Bye")}
or evenif c <- fmt.Sprintln("Hi") {fmt.Println("a")} else if c <- fmt.Sprintln("Hi") {fmt.Println("b")}and my favouriteswitch {case c <- fmt.Sprintf("%s %d", msg, i):// Do nothingcase <-quit:fmt.Println("Cleaning up...")quit <- true}}which looks exactly the same as a select statement but do something completely different. I think that having too many ways of doing the same thing complicates the process of understanding the code written by other people, without increasing code complexity.
if :: value = 0; :: value = 1; fi
On Tuesday, July 31, 2012 8:32:44 PM UTC+8, Marcelo Cantos wrote:
After giving this some more thought, I'm not at all convinced that the proposal to make send a bool-expression would complicate simple programs. Rob Pike's concurrency patterns talk delves into a quit channel about half way in, and it's about the simplest non-trivial program you can have. Here's a paraphrase of his example using current and send-returns-bool semantics (full example at http://play.golang.org/p/IgDmWyhknK)...// Currentfor i := 0; ; i++ {select {case c <- fmt.Sprintf("%s %d", msg, i):// Do nothingcase <-quit:fmt.Println("Cleaning up...")quit <- true}}// Send returns boolfor i := 0; c <- fmt.Sprintf("%s %d", msg, i); i++ {}fmt.Println("Cleaning up...")done <- trueMore complex examples may not benefit as much from expression semantics, but, after seeing this example, I would be surprised to see most simple programs become more complicated.
On Monday, July 30, 2012 11:24:56 PM UTC+10, Ian Lance Taylor wrote:
That would work with nil'd out channels, but there are also a lot of other ways it can block forever, such as when the other goroutine forgets about the channel, exits, or just simply won't ever send on it again. Doesn't seem worth its cognitive overhead.
and what would this do ?c1 <- c2 <- "Hi"
Do we send a value from c2 to c1, or do we send a bool to c1 ?