Sorry if a noob question. I just started learning Go yesterday, went through go tour, a bunch of blog posts, some example repos; and was feeling comfortable giving it a shot.
I am trying to read a file of email addresses, and return a channel from a function of mail.Address
type. I can't understand why it blocks without any output or deadlock
failure. Here is the code
package main
import (
"bufio"
"fmt"
"net/mail"
"os"
)
const addressListFile = "./emails.txt"
func GetAddressChan(path string) <-chan *mail.Address {
addrFile, err := os.Open(path)
defer addrFile.Close()
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(addrFile)
if err := scanner.Err(); err != nil {
panic(err)
}
addrCh := make(chan *mail.Address)
for scanner.Scan() {
addr, err := mail.ParseAddress(scanner.Text())
if err != nil {
panic(err)
}
addrCh <- addr
}
close(addrCh)
return addrCh
}
func main() {
addrCh := GetAddressChan(addressListFile)
go func() {
for addr := range addrCh {
fmt.Println(addr)
}
}()
}
It works if instead of return a channel from GetAddressChan
, I pass it one as an argument and run GetAddressChan
as a goroutine.
Any comments on how I am not writing idiomatic Go are welcome as well.
Edit: I've also tried putting the goroutine inside GetAddressChan
instead of main
as well, in which case the program exits without printing anything:
addrCh := make(chan *mail.Address)
go func() {
for scanner.Scan() {
addr, err := mail.ParseAddress(scanner.Text())
if err != nil {
panic(err)
}
addrCh <- addr
}
close(addrCh)
}()
return addrCh
Update: It is bufio.Scan
that is causing this issue I think. It works as expected if I use a normal slice/array of strings instead of reading using bufio.Scanner
评论:
Sythe2o0:
vehlad_durjan:
addrCh <- addr
blocks until something takes the thing its trying to send, which can't happen if the goroutine at the end of main hasn't spawned yet. You could reorganize it so thatGetAddressChan
had its second half after ago
keyword, or you could do what you suggested and pass in the channel to use.As far as idiomatic go goes, you probably already know that
panic
is generally to be avoided. It'd be more idiomatic ifGetAddressChan
returned a channel and an error.
Sythe2o0:Thank your for your note.
I don't want to have to pass the function a channel. I want it to return a channel so I can treat it as a generator. I have tried restructuring the code so the generator send part is in a goroutine, but even then it doesn't work.
Please check the update to the post.
vehlad_durjan:Any goroutines left running when the end of
main
is reached will stop execution. Adding in some blocking operation at the end ofmain
(or in this case, not putting the end of main aftergo
) will prevent this.
Sythe2o0:It doesn't seem to make a difference if I block the execution after
go
statement inmain
:ch := make(chan int) _ = <-ch
Only thing that seem to work is if I don't use any goroutine in either
main
orGetAddressChan
, but makeaddrCh
a buffered channel with buffer > number of email addresses in the file. (or if I block main from exiting and use said buffered channel)
vehlad_durjan:
Because your range over addrCh will end as soon as there's nothing left there, which will be immediately, because the goroutine sending things to the channel hasn't spawned yet.<- this is wrong, sorry I rarely range over channels myselfThen I'm not sure, can you make a link to a https://play.golang.org/ snippet with the updated code?
See this: https://play.golang.org/p/epTpwuycD0
Sythe2o0:Won't the goroutine for
addrCh
spaws as soon asGetAddressChan
gets called and blocks until someone receives a value? At least in the second case in which I wrapfor
loop sending the values toaddrCh
inGetAddressChan
in a goroutine instead of wrapping thefor
onrange
which receives values. It doesn't work in that case either.All this seemed easier when I was reading the code/doing examples only. It seem very much like a simple example code, but I am unable to figure out what I am doing wrong.
vehlad_durjan:I'm not following what you're saying. Can you write up what you're trying in a snippet? This is an example of how this would work, without using the mail stuff: https://play.golang.org/p/7yvWLDMy4M
bhiestand:It's
bufio.Scan
's doing I think. If I use a normal slice of strings instead ofbufio.Scan
, everything works as expected. I should study it more to find out what it is doing that is causing this, and how to work around it.
Use a go routine in the function, and not in main. You don't need a buffered channel. You want the program to run until all addresses have been printed, but main will exit as soon as it starts the printing goroutine, as written.
