Why is this code snippet blocking?

blov · 2017-09-10 19:00:07 · 677 次点击    
这是一个分享于 2017-09-10 19:00:07 的资源,其中的信息可能已经有所发展或是发生改变。

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:

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 that GetAddressChan had its second half after a go 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 if GetAddressChan returned a channel and an error.

vehlad_durjan:

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.

Sythe2o0:

Any goroutines left running when the end of main is reached will stop execution. Adding in some blocking operation at the end of main (or in this case, not putting the end of main after go) will prevent this.

vehlad_durjan:

It doesn't seem to make a difference if I block the execution after go statement in main:

ch := make(chan int)
_ = <-ch

Only thing that seem to work is if I don't use any goroutine in either main or GetAddressChan, but make addrCh a buffered channel with buffer > number of email addresses in the file. (or if I block main from exiting and use said buffered channel)

Sythe2o0:

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 myself

Then 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

vehlad_durjan:

Won't the goroutine for addrCh spaws as soon as GetAddressChan gets called and blocks until someone receives a value? At least in the second case in which I wrap for loop sending the values to addrCh in GetAddressChan in a goroutine instead of wrapping the for on range 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.

Sythe2o0:

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

vehlad_durjan:

It's bufio.Scan's doing I think. If I use a normal slice of strings instead of bufio.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.

bhiestand:

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.

https://play.golang.org/p/Fu6qVdDesZ


入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

677 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传