> func Query(conns []Conn, query string) Result {
> ch := make(chan Result, 1)
> for _, conn := range conns {
> go func(c Conn) {
> select {
> case ch <- c.DoQuery(query):
> default:
> }
> }(conn)
> }
> return <- ch
> }
>
> The idea is that if a connection doesn't "return immediately" the
> switch statement will fall through to the default condition so the
> goroutine isn't hanging around. I understand that much. The question I
> am trying to wrap my head around is what happens here if *none* of the
> "c.DoQuery(query)" calls return immediately?
All the DoQuery calls might take a long time to complete.
The channel send to ch only happens when a query has
completed. The first query that finishes will succeed in sending
its result to the channel (we know that for certain because the
channel has a buffer size of 1). That's the result that will
be returned from Query.
The second query that finishes will also succeed in sending
its result to the channel, if it does so after the first result has been
sent. however that result is never used. All the other result sends
will then block and fall through to the default clause accordingly.
Does that make things clearer?
It's a simple resource usage vs. performance trade off. If all the
machines are under-used, and it's critical to return an answer as fast
as possible, you could use that technique.
--
Gustavo Niemeyer
http://niemeyer.net
http://niemeyer.net/plus
http://niemeyer.net/twitter
http://niemeyer.net/blog
-- I never filed a patent.
I think his original point was that if the "default:" section is taken for each conn, because it is slow, then the final return will hang and block.
I think the model is confusing, because it certainly is a rather special case.
I'm also a bit surprised that this works, but I guess if DoQuery puts data in its return channel before it hands the channel back, it could work.
I think the more obvious model would be to pass the go routine an "exit" channel. And then have them select from the DoQuery or exit. The outer routine waits for the first response, then signals the goroutines to exit. Im guessing it should do something about draining the return channel, since otherwise we have a bunch of blocked goroutines. And IIRC, blocked routines aren't gc'd.
John
=:->
this cannot happen.
>
> I think the model is confusing, because it certainly is a rather special case.
> I'm also a bit surprised that this works, but I guess if DoQuery puts data in its return channel before it hands the channel back, it could work.
I'm not sure what you are getting at here. DoQuery doesn't have a
return channel - it returns a
result that is then sent on a channel.
perhaps the example might have been clearer if it had
been written like this, which is functionally identical:
func Query(conns []Conn, query string) Result {
ch := make(chan Result, 1)
for _, conn := range conns {
go func(c Conn) {
r := c.DoQuery(query)
select {
case ch <- r:
default:
}
}(conn)
}
return <- ch
}
> I think the more obvious model would be to pass the go routine an "exit" channel. And then have them select from the DoQuery or exit.
the goroutines are no selecting from the DoQuery - they're selecting
on the send to ch, which is quite different.
one potentially legitimate gripe about the above code is that
if one DoQuery succeeds quickly then for as long as the
others hang around they are garbage which can't be collected.
if you're doing hundreds of requests a second, this might become
an issue. (it is solvable too, if the db connection allows some way
of cancelling outstanding requests, but that's outside the scope
of the example).