goroutine channel的坑

channel的关闭

  • for-range 知道channel的关闭,代码继续执行
  • for-select 不知道,用case v, ok:=<-c:中的ok来判断
    • 那么问题来了,如果是一个buffer channel,ok在关闭的时候可不是true,而是满足channel数据去完&channel closeok才会返回true,如果不这样设计的话,会导致数据丢失。

channel for-select 中break和return的问题

核心是,break在select中是只能跳出selectreturn是返回当前的函数。

package meituri

import "fmt"

type ISuiteOperator interface {
    GetPageURLs(chan string)
    GetImgURLs(chPage <-chan string, chFailedImg <-chan string) <-chan string
    Download(chImg <-chan string, chFailedImg chan string, folderPath string) <-chan string
}

// DonwloadSuite to download one suite
func DonwloadSuite(iSuite ISuiteOperator, countFanOut int, folderPath string) {
    chPage := make(chan string)
    chFailedImg := make(chan string) // 下载失败img放回
    go iSuite.GetPageURLs(chPage)
    var chImgs []<-chan string
    for i := 0; i < countFanOut; i++ {
        ch := iSuite.GetImgURLs(chPage, chFailedImg)
        chImgs = append(chImgs, ch)
    }
    // 回收多个channel的结果
    chImg := merge(chImgs...)
    // for debug
    // for {
    // 	select {
    // 	case v, ok := <-chImg:
    // 		if !ok {
    // 			return
    // 		}
    // 		fmt.Println(v, ok)
    // 	}
    // }
    var chDownloads []<-chan string
    for i := 0; i < countFanOut; i++ {
        ch := iSuite.Download(chImg, chFailedImg, folderPath)
        chDownloads = append(chDownloads, ch)
    }
    finish := merge(chDownloads...)
    for ret := range finish {
        fmt.Println("finish: ", ret)
    }
}

在上面的代码中,chImg在merge结果的时候,注释代码是去测试结果的。

  • 如果在chImg关闭的时候return,此时应该是退出DonwloadSuite这个方法,注意此时使用break只是break select而已,还是在for循环中,这是golang for-select的大坑
  • 比如在下面的代码中,在go func(){}中使用return是退出当前的goroutine,可以触发goroutine中注册的defer来关闭已经写入完毕的channel以达到让使用者感知通信结束,使用for-range可以直接感知,for-select需要用case val, ok:= <-c:中的ok的bool值判断channel是否关闭
func (suite MeituriSuite) GetImgURLs(chPage <-chan string, chFailedImg <-chan string) <-chan string {
    out := make(chan string)

    go func() {
        defer close(out)
        for {
            select {
            case url, ok := <-chPage:
                if !ok {
                    fmt.Println("no more url")
                    return
                }
                content := getPageContent(url)
                divContent := parseDivContent(content)
                imgSrcs := parseImg(divContent)
                // fmt.Println(imgSrcs)
                for _, imgSrc := range imgSrcs {
                    out <- imgSrc
                }
            case url := <-chFailedImg:
                out <- url
            }
        }
    }()
    return out
}