As others already noted, you can solve this problem a lot more simply:
type sized struct {
url string
length int
err error
}
func sizer(url string, result chan<- sized, wg *sync.WaitGroup) {
defer wg.Done()
length, err := getPage(url)
result <- sized{url, length, err}
}
func main() {
urls := []string{"http://www.google.com/", "http://www.yahoo.com",
"http://www.bing.com", "http://bbc.co.uk"}
ch := make(chan sized)
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go sizer(url, ch, &wg)
}
go func() {
wg.Wait()
close(ch)
}()
for result := range ch {
if result.err != nil {
fmt.Printf("%s: %s\n", result.url, result.err)
} else {
fmt.Printf("%s: length = %d\n", result.url, result.length)
}
}
}
Complete example on playground, though it can't dial out, so not really useful there.
How to perform bi-directional communication on a channel?
You can't.
Well, that's not entirely true. Let's say instead: you shouldn't.
Suppose we have a channel:
var ch chan T
for some type T.
The channel itself is inherently ... well, I think non-directional is the right word:
- anyone can put any value of type T into the channel with
ch <- val
- anyone can take any value of type T out of the channel with
var <- ch
This clearly isn't unidirectional, but I think calling it bidirectional is misleading: it has a "put things in" side and a "take things out" side, both lumped into one channel instance, but the channel isn't associated with any particular user. You can copy the channel value to a directional instance (see What's the point of one-way channels in Go?), in either direction, but both are really just references to an underlying non-directional channel object.1 You can then pass the original channel, or a unidirectional-ized copy of it, to any user, or treat any of these as "global" (wide-scope) variables that anyone can use.
Now let's add two entities, which we'll call X and Y. If channel ch
is buffered, either or both of X and Y can put items in, and then either or both of A and B can take items out. Items will come out in the order they went in, serializing access; if the channel is full, the attempt to put an item in will block; but items do not transfer specifically from X to Y. In particular:
// Y: paused or stopped
// do this in X when ch is initially empty:
ch <- T{}
v := <-ch
Here, X puts a zero-valued T
into the channel, then takes it back out. This isn't directional. X got its own data back.
If the channel isn't empty, but also isn't full, either X or Y can add something to it (as a queue), then take something off the front of the queue. But that's just using the channel as a queue. (See Is it possible to use Go's buffered channel as a thread-safe queue?)
If this queue had certain features that it definitely lacks, you could use a single queue for a sort of bidirectional communication (see Is two way comm possible using a single message queue in C). But it doesn't have them. If you really need bidirectional communication, the answer is obvious: use two queues. Designate one queue—one channel—as the X-sends-to-Y channel, and the other as the Y-sends-to-X channel.
1The underlying channel is accessible / recoverable via unsafe
, but don't do that unless you really know exactly what you're doing. Someone who passed you a unidirectional instance probably doesn't intend for you to use the other "side" of the channel.