通道奇怪的结果去goroutine

时间:2016-02-08 05:25:17

标签: go goroutine

当我运行goroutines时,我通常得到40作为值,我知道它的并发性,但为什么最后一个数字会出现?我想输出必须是:

Page number:  34  
Page number:  12  
Page number:  8  
Page number:  2  
Page number:  29

示例源代码

package main

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

func getWebPageContent(url string, c chan int, val int) interface{} {

    if r, err := http.Get(url); err == nil {
        defer r.Body.Close()
        if body, err := ioutil.ReadAll(r.Body); err == nil {
            c <- val
            return string(body)
        }
    } else {
        fmt.Println(err)
    }
    return "XoX"

}

const MAX_TH = 40

func main() {

    // pln := fmt.Println
    messages := make(chan int)
    for j := 0; j < MAX_TH; j++ {
        go func() { getWebPageContent("http://www.example.com", messages, j) }()
    }

    routine_count := 0
    var page_number int
    for {
        page_number = <-messages
        routine_count++
        fmt.Println("Page number: ", page_number)
        if routine_count == MAX_TH {
            break
        }
    }
    close(messages)
}

2 个答案:

答案 0 :(得分:5)

  

Go编程语言

     

Frequently Asked Questions (FAQ)

     

What happens with closures running as goroutines?

     

使用并发闭包时可能会出现一些混淆。   请考虑以下程序:

func main() {
    done := make(chan bool)

    values := []string{"a", "b", "c"}
    for _, v := range values {
        go func() {
            fmt.Println(v)
            done <- true
        }()
    }

    // wait for all goroutines to complete before exiting
    for _ = range values {
        <-done
    }
}
     

有人可能错误地期望看到a,b,c作为输出。你要做什么   可能看到的是c,c,c。这是因为每次迭代都是如此   loop使用变量v的相同实例,因此每个闭包共享   那个单变量。当闭包运行时,它会打印v的值   在fmt.Println执行时,但v可能已被修改   自从goroutine发布以来。帮助检测这个和其他   问题发生之前,请去看兽医。

     

在启动时将v的当前值绑定到每个闭包,一个   必须修改内部循环以在每次迭代时创建一个新变量。   一种方法是将变量作为参数传递给闭包:

for _, v := range values {
    go func(u string) {
        fmt.Println(u)
        done <- true
    }(v)
}
     

在此示例中,v的值作为参数传递给   匿名功能。然后可以在函数内访问该值   作为变量u。

     

更简单的方法就是使用声明创建一个新变量   可能看似奇怪但在Go中工作正常的风格:

for _, v := range values {
    v := v // create a new 'v'.
    go func() {
        fmt.Println(v)
        done <- true
    }()
}

因此,在您的情况下,通过添加语句j := j

来创建新变量
for j := 0; j < MAX_TH; j++ {
    j := j
    go func() { getWebPageContent("http://www.example.com", messages, j) }()
}

例如,

package main

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

func getWebPageContent(url string, c chan int, val int) interface{} {
    if r, err := http.Get(url); err == nil {
        defer r.Body.Close()
        if body, err := ioutil.ReadAll(r.Body); err == nil {
            c <- val
            return string(body)
        }
    } else {
        fmt.Println(err)
    }
    return "XoX"
}

const MAX_TH = 40

func main() {

    // pln := fmt.Println
    messages := make(chan int)
    for j := 0; j < MAX_TH; j++ {
        j := j
        go func() { getWebPageContent("http://www.example.com", messages, j) }()
    }

    routine_count := 0
    var page_number int
    for {
        page_number = <-messages
        routine_count++
        fmt.Println("Page number: ", page_number)
        if routine_count == MAX_TH {
            break
        }
    }
    close(messages)
}

输出:

Page number:  23
Page number:  6
Page number:  1
Page number:  3
Page number:  28
Page number:  32
Page number:  18
Page number:  22
Page number:  0
Page number:  36
Page number:  7
Page number:  21
Page number:  12
Page number:  2
Page number:  5
Page number:  4
Page number:  33
Page number:  13
Page number:  20
Page number:  27
Page number:  29
Page number:  8
Page number:  31
Page number:  10
Page number:  17
Page number:  25
Page number:  19
Page number:  35
Page number:  14
Page number:  38
Page number:  15
Page number:  30
Page number:  37
Page number:  39
Page number:  26
Page number:  9
Page number:  16
Page number:  11
Page number:  24
Page number:  34

答案 1 :(得分:1)

我的第一个golang回复,可能完全关闭: - )

循环可能看起来像这样:

...
for j := 0; j < MAX_TH; j++ {
    go func(x) { getWebPageContent("http://www.example.com", messages, x) }(j)
}
...

基本上,您定义一个匿名函数并使用参数调用它。你可以采用不同的方式,但这个解决方案看起来非常实用和时尚: - )