【问题标题】:For loop with buffered channel带缓冲通道的循环
【发布时间】:2018-06-24 13:54:47
【问题描述】:

我正在尝试使用 Go 通道,但遇到以下简单程序无法终止的问题。

基本上我想发出一些异步 HTTP 获取请求,然后等待直到它们全部完成。我正在使用缓冲通道,但我不确定这是惯用的方式。

func GetPrice(quotes chan string) {
    client := &http.Client{}
    req, _ := http.NewRequest("GET", "https://some/api", nil)
    req.Header.Set("Accept", "application/json")
    res, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    quotes <- string(body)
}

func main() {
    const max = 3
    quotes := make(chan string, max)
    for i := 0; i < max; i++ {
        go GetPrice(quotes)
    }

    for n := range quotes {
        fmt.Printf("\n%s", n)
    }
}

程序成功打印 3(max) 个项目

{"price":"1.00"}
{"price":"2.00"}
{"price":"3.00"}

但随后会阻塞并且永远不会退出。

【问题讨论】:

  • 它永远不会退出,因为你还没有以任何方式结束 for 循环。您通过关闭 chan 来表明不再从 chan 接收任何值。

标签: for-loop go channel buffered


【解决方案1】:

你可以做的另一种方式:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func GetPrice(quotes chan string) {
    client := &http.Client{}
    req, _ := http.NewRequest("GET", "https://some/api", nil)
    req.Header.Set("Accept", "application/json")
    res, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    quotes <- string(body)
}

func run(quotes chan string, quit chan bool, max int) {
    for num := range quotes {
        fmt.Println(num)
        max--

        if max == 0 {
            quit <- true
        }
    }
}

func main() {
    const max = 3
    quotes := make(chan string)
    quit := make(chan bool)

    go run(quotes, quit, max)

    for i := 0; i < max; i++ {
        go GetPrice(quotes)
    }

    <-quit
}

【讨论】:

    【解决方案2】:

    这里可以使用sync.WaitGroup等待所有goroutine然后关闭quotes通道:

    func getPrice(quotes chan<- string, onExit func()) {
        go func() {
            defer onExit()
    
            req, _ := http.NewRequest("GET", "https://some/api", nil)
            req.Header.Set("Accept", "application/json")
    
            client := &http.Client{}
            res, err := client.Do(req)
            if err != nil {
                panic(err) // should be handled properly
            }
            defer res.Body.Close()
    
            body, err := ioutil.ReadAll(res.Body)
            quotes <- string(body)
        }()
    }
    
    func main() {
        const max = 3
        var wg sync.WaitGroup
    
        quotes := make(chan string, max)
        for i := 0; i < max; i++ {
            wg.Add(1)
            getPrice(quotes, func() { wg.Done() })
        }
    
        go func() {
            defer close(quotes)
            wg.Wait()
        }()
    
        for n := range quotes {
            fmt.Printf("\n%s", n)
        }
    }
    

    【讨论】:

    • 我会选择这个答案,因为它在逻辑上代表了我正在尝试做的事情。
    【解决方案3】:
    func GetPrice(quotes chan string) {
        client := &http.Client{}
        req, _ := http.NewRequest("GET", "https://some/api", nil)
        req.Header.Set("Accept", "application/json")
        res, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        defer res.Body.Close()
        body, err := ioutil.ReadAll(res.Body)
        quotes <- string(body)
    }
    
    func Consumer(chan string){
        for n ,ok:= range quotes {
            if !ok {
                 break
            }
            fmt.Printf("\n%s", n)
        }
    }
    
    func main() {
        const max = 3
        quotes := make(chan string, max)
        for i := 0; i < max; i++ {
            go GetPrice(quotes)
        }
        go Consumer(quotes)
        close(quotes)
    }
    

    【讨论】:

    • 虽然此代码可以回答问题,但提供有关其解决问题的方式和原因的信息可提高其长期价值。
    • 根据文档,更高级别的同步最好通过渠道和通信来完成。可以作为使用chans的解决方案
    猜你喜欢
    • 2013-09-26
    • 2014-01-30
    • 2018-07-25
    • 2014-05-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-12
    相关资源
    最近更新 更多